From d6c8d41ce0d5bdcee94befe676be7137de0b5cf0 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:08:27 +0800
Subject: [PATCH 01/13] Create readme.md
---
itchat/readme.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 itchat/readme.md
diff --git a/itchat/readme.md b/itchat/readme.md
new file mode 100644
index 00000000..f4ccca8f
--- /dev/null
+++ b/itchat/readme.md
@@ -0,0 +1,7 @@
+As itchat is multi-threaded, it's impossible to catch the internet errors it has produced. So I added some callbacks.
+
+In core.functionDict, I added the key "Error" whose value is a list, where we may store error-handling functions.
+
+Use '@itchat.error_register(True)' to register your error-handling function.
+
+这玩意多线程导致我没法捕捉错误,所以搞了个回调。用@itchat.error_register(True)来注册你的错误处理函数。
From 6ea722599ccbb9e5b9e187214bdc0effdb0e7a99 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:09:29 +0800
Subject: [PATCH 02/13] Delete __init__.py
---
itchat/__init__.py | 67 ----------------------------------------------
1 file changed, 67 deletions(-)
delete mode 100644 itchat/__init__.py
diff --git a/itchat/__init__.py b/itchat/__init__.py
deleted file mode 100644
index 256fc721..00000000
--- a/itchat/__init__.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from . import content
-from .core import Core
-from .config import VERSION
-from .log import set_logging
-
-__version__ = VERSION
-
-instanceList = []
-
-def new_instance():
- newInstance = Core()
- instanceList.append(newInstance)
- return newInstance
-
-originInstance = new_instance()
-
-# I really want to use sys.modules[__name__] = originInstance
-# but it makes auto-fill a real mess, so forgive me for my following **
-# actually it toke me less than 30 seconds, god bless Uganda
-
-# components.login
-login = originInstance.login
-get_QRuuid = originInstance.get_QRuuid
-get_QR = originInstance.get_QR
-check_login = originInstance.check_login
-web_init = originInstance.web_init
-show_mobile_login = originInstance.show_mobile_login
-start_receiving = originInstance.start_receiving
-get_msg = originInstance.get_msg
-logout = originInstance.logout
-# components.contact
-update_chatroom = originInstance.update_chatroom
-update_friend = originInstance.update_friend
-get_contact = originInstance.get_contact
-get_friends = originInstance.get_friends
-get_chatrooms = originInstance.get_chatrooms
-get_mps = originInstance.get_mps
-set_alias = originInstance.set_alias
-set_pinned = originInstance.set_pinned
-add_friend = originInstance.add_friend
-get_head_img = originInstance.get_head_img
-create_chatroom = originInstance.create_chatroom
-set_chatroom_name = originInstance.set_chatroom_name
-delete_member_from_chatroom = originInstance.delete_member_from_chatroom
-add_member_into_chatroom = originInstance.add_member_into_chatroom
-# components.messages
-send_raw_msg = originInstance.send_raw_msg
-send_msg = originInstance.send_msg
-upload_file = originInstance.upload_file
-send_file = originInstance.send_file
-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
-# components.register
-auto_login = originInstance.auto_login
-configured_reply = originInstance.configured_reply
-msg_register = originInstance.msg_register
-run = originInstance.run
-# other functions
-search_friends = originInstance.search_friends
-search_chatrooms = originInstance.search_chatrooms
-search_mps = originInstance.search_mps
-set_logging = set_logging
From 603c078913674566dbafbb672224e53dff9128aa Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:11:56 +0800
Subject: [PATCH 03/13] Add files via upload
---
itchat/__init__.py | 69 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 itchat/__init__.py
diff --git a/itchat/__init__.py b/itchat/__init__.py
new file mode 100644
index 00000000..a7cb466b
--- /dev/null
+++ b/itchat/__init__.py
@@ -0,0 +1,69 @@
+from . import content
+from .core import Core
+from .config import VERSION
+from .log import set_logging
+
+__version__ = VERSION
+
+instanceList = []
+
+def new_instance():
+ newInstance = Core()
+ instanceList.append(newInstance)
+ return newInstance
+
+originInstance = new_instance()
+
+# I really want to use sys.modules[__name__] = originInstance
+# but it makes auto-fill a real mess, so forgive me for my following **
+# actually it toke me less than 30 seconds, god bless Uganda
+
+# components.login
+login = originInstance.login
+get_QRuuid = originInstance.get_QRuuid
+get_QR = originInstance.get_QR
+check_login = originInstance.check_login
+web_init = originInstance.web_init
+show_mobile_login = originInstance.show_mobile_login
+start_receiving = originInstance.start_receiving
+get_msg = originInstance.get_msg
+logout = originInstance.logout
+# components.contact
+update_chatroom = originInstance.update_chatroom
+update_friend = originInstance.update_friend
+get_contact = originInstance.get_contact
+get_friends = originInstance.get_friends
+get_chatrooms = originInstance.get_chatrooms
+get_mps = originInstance.get_mps
+set_alias = originInstance.set_alias
+set_pinned = originInstance.set_pinned
+add_friend = originInstance.add_friend
+get_head_img = originInstance.get_head_img
+create_chatroom = originInstance.create_chatroom
+set_chatroom_name = originInstance.set_chatroom_name
+delete_member_from_chatroom = originInstance.delete_member_from_chatroom
+add_member_into_chatroom = originInstance.add_member_into_chatroom
+# components.messages
+send_raw_msg = originInstance.send_raw_msg
+send_msg = originInstance.send_msg
+upload_file = originInstance.upload_file
+send_file = originInstance.send_file
+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
+# components.register
+auto_login = originInstance.auto_login
+configured_reply = originInstance.configured_reply
+msg_register = originInstance.msg_register
+error_register = originInstance.error_register
+run = originInstance.run
+# other functions
+search_friends = originInstance.search_friends
+search_chatrooms = originInstance.search_chatrooms
+search_mps = originInstance.search_mps
+set_logging = set_logging
+
From b83c7c703f5f7cf64462ff61a93270faad519b56 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:12:38 +0800
Subject: [PATCH 04/13] Update README.md
---
README.md | 307 +-----------------------------------------------------
1 file changed, 4 insertions(+), 303 deletions(-)
diff --git a/README.md b/README.md
index 75767d9b..acb2d47b 100644
--- a/README.md
+++ b/README.md
@@ -1,306 +1,7 @@
-# itchat
+As itchat is multi-threaded, it's impossible to catch the internet errors it has produced. So I added some callbacks.
-[![Gitter][gitter-picture]][gitter] ![py27][py27] ![py35][py35] [English version][english-version]
+In core.functionDict, I added the key "Error" whose value is a list, where we may store error-handling functions.
-itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。
+Use '@itchat.error_register(True)' to register your error-handling function.
-使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。
-
-当然,该api的使用远不止一个机器人,更多的功能等着你来发现,比如[这些][tutorial2]。
-
-该接口与公众号接口[itchatmp][itchatmp]共享类似的操作方式,学习一次掌握两个工具。
-
-如今微信已经成为了个人社交的很大一部分,希望这个项目能够帮助你扩展你的个人的微信号、方便自己的生活。
-
-## 安装
-
-可以通过本命令安装itchat:
-
-```python
-pip install itchat
-```
-
-## 简单入门实例
-
-有了itchat,如果你想要给文件传输助手发一条信息,只需要这样:
-
-```python
-import itchat
-
-itchat.auto_login()
-
-itchat.send('Hello, filehelper', toUserName='filehelper')
-```
-
-如果你想要回复发给自己的文本消息,只需要这样:
-
-```python
-import itchat
-
-@itchat.msg_register(itchat.content.TEXT)
-def text_reply(msg):
- return msg.text
-
-itchat.auto_login()
-itchat.run()
-```
-
-一些进阶应用可以在下面的开源机器人的源码和进阶应用中看到,或者你也可以阅览[文档][document]。
-
-## 试一试
-
-这是一个基于这一项目的[开源小机器人][robot-source-code],百闻不如一见,有兴趣可以尝试一下。
-
-由于好友数量实在增长过快,自动通过好友验证的功能演示暂时关闭。
-
-![QRCode][robot-qr]
-
-## 截屏
-
-![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
-
-## 进阶应用
-
-### 特殊的字典使用方式
-
-通过打印itchat的用户以及注册消息的参数,可以发现这些值都是字典。
-
-但实际上itchat精心构造了相应的消息、用户、群聊、公众号类。
-
-其所有的键值都可以通过这一方式访问:
-
-```python
-@itchat.msg_register(TEXT)
-def _(msg):
- # equals to print(msg['FromUserName'])
- print(msg.fromUserName)
-```
-
-属性名为键值首字母小写后的内容。
-
-```python
-author = itchat.search_friends(nickName='LittleCoder')[0]
-author.send('greeting, littlecoder!')
-```
-
-### 各类型消息的注册
-
-通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
-
-```python
-import itchat, time
-from itchat.content import *
-
-@itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
-def text_reply(msg):
- msg.user.send('%s: %s' % (msg.type, msg.text))
-
-@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
-def download_files(msg):
- msg.download(msg.fileName)
- typeSymbol = {
- PICTURE: 'img',
- VIDEO: 'vid', }.get(msg.type, 'fil')
- return '@%s@%s' % (typeSymbol, msg.fileName)
-
-@itchat.msg_register(FRIENDS)
-def add_friend(msg):
- msg.user.verify()
- msg.user.send('Nice to meet you!')
-
-@itchat.msg_register(TEXT, isGroupChat=True)
-def text_reply(msg):
- if msg.isAt:
- msg.user.send(u'@%s\u2005I received: %s' % (
- msg.actualNickName, msg.text))
-
-itchat.auto_login(True)
-itchat.run(True)
-```
-
-### 命令行二维码
-
-通过以下命令可以在登陆的时候使用命令行显示二维码:
-
-```python
-itchat.auto_login(enableCmdQR=True)
-```
-
-部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
-
-```python
-# 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
-itchat.auto_login(enableCmdQR=2)
-```
-
-默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
-
-```python
-itchat.auto_login(enableCmdQR=-1)
-```
-
-### 退出程序后暂存登陆状态
-
-通过如下命令登陆,即使程序关闭,一定时间内重新开启也可以不用重新扫码。
-
-```python
-itchat.auto_login(hotReload=True)
-```
-
-### 用户搜索
-
-使用`search_friends`方法可以搜索用户,有四种搜索方式:
-1. 仅获取自己的用户信息
-2. 获取特定`UserName`的用户信息
-3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
-4. 获取备注、微信号、昵称分别等于相应键值的用户
-
-其中三、四项可以一同使用,下面是示例程序:
-
-```python
-# 获取自己的用户信息,返回自己的属性字典
-itchat.search_friends()
-# 获取特定UserName的用户信息
-itchat.search_friends(userName='@abcdefg1234567')
-# 获取任何一项等于name键值的用户
-itchat.search_friends(name='littlecodersh')
-# 获取分别对应相应键值的用户
-itchat.search_friends(wechatAccount='littlecodersh')
-# 三、四项功能可以一同使用
-itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
-```
-
-关于公众号、群聊的获取与搜索在文档中有更加详细的介绍。
-
-### 附件的下载与发送
-
-itchat的附件下载方法存储在msg的Text键中。
-
-发送的文件的文件名(图片给出的默认文件名)都存储在msg的FileName键中。
-
-下载方法接受一个可用的位置参数(包括文件名),并将文件相应的存储。
-
-```python
-@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
-def download_files(msg):
- msg.download(msg.fileName)
- itchat.send('@%s@%s' % (
- 'img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']),
- msg['FromUserName'])
- return '%s received' % msg['Type']
-```
-
-如果你不需要下载到本地,仅想要读取二进制串进行进一步处理可以不传入参数,方法将会返回图片的二进制串。
-
-```python
-@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
-def download_files(msg):
- with open(msg.fileName, 'wb') as f:
- f.write(msg.download())
-```
-
-### 用户多开
-
-使用如下命令可以完成多开的操作:
-
-```python
-import itchat
-
-newInstance = itchat.new_instance()
-newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
-
-@newInstance.msg_register(itchat.content.TEXT)
-def reply(msg):
- return msg.text
-
-newInstance.run()
-```
-
-### 退出及登陆完成后调用特定方法
-
-登陆完成后的方法需要赋值在`loginCallback`中。
-
-而退出后的方法需要赋值在`exitCallback`中。
-
-```python
-import time
-
-import itchat
-
-def lc():
- print('finish login')
-def ec():
- print('exit')
-
-itchat.auto_login(loginCallback=lc, exitCallback=ec)
-time.sleep(3)
-itchat.logout()
-```
-
-若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。
-
-## 常见问题与解答
-
-Q: 如何通过这个包将自己的微信号变为控制器?
-
-A: 有两种方式:发送、接受自己UserName的消息;发送接收文件传输助手(filehelper)的消息
-
-Q: 为什么我发送信息的时候部分信息没有成功发出来?
-
-A: 有些账号是天生无法给自己的账号发送信息的,建议使用`filehelper`代替。
-
-## 作者
-
-[LittleCoder][littlecodersh]: 构架及维护Python2 Python3版本。
-
-[tempdban][tempdban]: 协议、构架及日常维护。
-
-[Chyroc][Chyroc]: 完成第一版本的Python3构架。
-
-## 类似项目
-
-[youfou/wxpy][youfou-wxpy]: 优秀的api包装和配套插件,微信机器人/优雅的微信个人号API
-
-[liuwons/wxBot][liuwons-wxBot]: 类似的基于Python的微信机器人
-
-[zixia/wechaty][zixia-wechaty]: 基于Javascript(ES6)的微信个人账号机器人NodeJS框架/库
-
-[sjdy521/Mojo-Weixin][Mojo-Weixin]: 使用Perl语言编写的微信客户端框架,可通过插件提供基于HTTP协议的api接口供其他语言调用
-
-[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, 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
-[py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
-[py35]: https://img.shields.io/badge/python-3.5-red.svg
-[english-version]: https://github.com/littlecodersh/ItChat/blob/master/README_EN.md
-[itchatmp]: https://github.com/littlecodersh/itchatmp
-[document]: https://itchat.readthedocs.org/zh/latest/
-[tutorial2]: http://python.jobbole.com/86532/
-[robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
-[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/
-[littlecodersh]: https://github.com/littlecodersh
-[tempdban]: https://github.com/tempdban
-[Chyroc]: https://github.com/Chyroc
-[youfou-wxpy]: https://github.com/youfou/wxpy
-[liuwons-wxBot]: https://github.com/liuwons/wxBot
-[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
+这玩意多线程导致我没法捕捉错误,所以搞了个回调。用@itchat.error_register(True)来注册你的错误处理函数。
From f4aebe7db1751ed1dda76df192d881ad9f7cc969 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:13:00 +0800
Subject: [PATCH 05/13] Delete README_EN.md
---
README_EN.md | 293 ---------------------------------------------------
1 file changed, 293 deletions(-)
delete mode 100644 README_EN.md
diff --git a/README_EN.md b/README_EN.md
deleted file mode 100644
index ab6df03d..00000000
--- a/README_EN.md
+++ /dev/null
@@ -1,293 +0,0 @@
-# itchat
-
-[![Gitter][gitter-picture]][gitter] ![py27][py27] ![py35][py35] [Chinese version][chinese-version]
-
-itchat is an open source api for WeChat, a commonly-used Chinese social networking app.
-
-Accessing your personal wechat account through itchat in python has never been easier.
-
-A wechat robot can handle all the basic messages with only less than 30 lines of codes.
-
-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 better user's experience with wechat.
-
-## Installation
-
-itchat can be installed with this little one-line command:
-
-```python
-pip install itchat
-```
-
-## Simple uses
-
-With itchat, if you want to send a message to filehelper, this is how:
-
-```python
-import itchat
-
-itchat.auto_login()
-
-itchat.send('Hello, filehelper', toUserName='filehelper')
-```
-
-And you only need to write this to reply personal text messages.
-
-```python
-import itchat
-
-@itchat.msg_register(itchat.content.TEXT)
-def text_reply(msg):
- return msg.text
-
-itchat.auto_login()
-itchat.run()
-```
-
-For more advanced uses you may continue on reading or browse the [document][document].
-
-## Have a try
-
-This QRCode is a wechat account based on the framework of [demo code][robot-source-code]. Seeing is believing, so have a try:)
-
-![QRCode][robot-qr]
-
-## Screenshots
-
-![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
-
-## Advanced uses
-
-### Special usage of message dictionary
-
-You may find out that all the users and messages of itchat are dictionaries by printing them out onto the screen.
-
-But actually they are useful classes itchat created.
-
-They have useful keys and useful interfaces, like:
-
-```python
-@itchat.msg_register(TEXT)
-def _(msg):
- # equals to print(msg['FromUserName'])
- print(msg.fromUserName)
-```
-
-And like:
-
-```python
-author = itchat.search_friends(nickName='LittleCoder')[0]
-author.send('greeting, littlecoder!')
-```
-
-### Message register of various types
-
-The following is a demo of how itchat is configured to fetch and reply daily information.
-
-```python
-import itchat, time
-from itchat.content import *
-
-@itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
-def text_reply(msg):
- msg.user.send('%s: %s' % (msg.type, msg.text))
-
-@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
-def download_files(msg):
- msg.download(msg.fileName)
- typeSymbol = {
- PICTURE: 'img',
- VIDEO: 'vid', }.get(msg.type, 'fil')
- return '@%s@%s' % (typeSymbol, msg.fileName)
-
-@itchat.msg_register(FRIENDS)
-def add_friend(msg):
- msg.user.verify()
- msg.user.send('Nice to meet you!')
-
-@itchat.msg_register(TEXT, isGroupChat=True)
-def text_reply(msg):
- if msg.isAt:
- msg.user.send(u'@%s\u2005I received: %s' % (
- msg.actualNickName, msg.text))
-
-itchat.auto_login(True)
-itchat.run(True)
-```
-
-### Command line QR Code
-
-You can access the QR Code in command line through using this command:
-
-```python
-itchat.auto_login(enableCmdQR=True)
-```
-
-Because of width of some character differs from systems, you may adjust the enableCmdQR to fix the problem.
-
-```python
-# for some linux system, width of block character is one instead of two, so enableCmdQR should be 2
-itchat.auto_login(enableCmdQR=2)
-```
-
-Default background color of command line is dark (black), if it's not, you may set enableCmdQR to be negative:
-
-```python
-itchat.auto_login(enableCmdQR=-1)
-```
-
-### Hot reload
-
-By using the following command, you may reload the program without re-scan QRCode in some time.
-
-```python
-itchat.auto_login(hotReload=True)
-```
-
-### User search
-
-By using `search_friends`, you have four ways to search a user:
-1. Get your own user information
-2. Get user information through `UserName`
-3. Get user information whose remark name or wechat account or nickname matches name key of the function
-4. Get user information whose remark name, wechat account and nickname match what are given to the function
-
-Way 3, 4 can be used together, the following is the demo program:
-
-```python
-# get your own user information
-itchat.search_friends()
-# get user information of specific username
-itchat.search_friends(userName='@abcdefg1234567')
-# get user information of function 3
-itchat.search_friends(name='littlecodersh')
-# get user information of function 4
-itchat.search_friends(wechatAccount='littlecodersh')
-# combination of way 3, 4
-itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
-```
-
-There is detailed information about searching and getting of massive platforms and chatrooms in document.
-
-### Download and send attachments
-
-The attachment download function of itchat is in Text key of msg
-
-Name of the file (default name of picture) is in FileName key of msg
-
-Download function accept one location value (include the file name) and store attachment accordingly.
-
-```python
-@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
-def download_files(msg):
- msg.download(msg.fileName)
- itchat.send('@%s@%s' % (
- 'img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']),
- msg['FromUserName'])
- return '%s received' % msg['Type']
-```
-
-If you don't want a local copy of the picture, you may pass nothing to the function to get a binary string.
-
-```python
-@itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
-def download_files(msg):
- with open(msg.fileName, 'wb') as f:
- f.write(msg.download())
-```
-
-### Multi instance
-
-You may use the following commands to open multi instance.
-
-```python
-import itchat
-
-newInstance = itchat.new_instance()
-newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
-
-@newInstance.msg_register(itchat.content.TEXT)
-def reply(msg):
- return msg['Text']
-
-newInstance.run()
-```
-
-### Set callback after login and logout
-
-Callback of login and logout are set through `loginCallback` and `exitCallback`.
-
-```python
-import time
-
-import itchat
-
-def lc():
- print('finish login')
-def ec():
- print('exit')
-
-itchat.auto_login(loginCallback=lc, exitCallback=ec)
-time.sleep(3)
-itchat.logout()
-```
-
-If loginCallback is not set, qr picture will be deleted and cmd will be cleared.
-
-If you exit through phone, exitCallback will also be called.
-
-## FAQ
-
-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.
-
-Q: Why sometimes I can't send messages?
-
-A: Some account simply can't send messages to yourself, so use `filehelper` instead.
-
-## Author
-
-[LittleCoder][littlecodersh]: Structure and py2 py3 version
-
-[tempdban][tempdban]: Structure and daily maintainance
-
-[Chyroc][Chyroc]: first py3 version
-
-## See also
-
-[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
-
-[sjdy521/Mojo-Weixin][Mojo-Weixin]: Wechat web api in Perl, available with HTTP requests
-
-[yaphone/itchat4j][yaphone-itchat4j]: Extend your wechat with java
-
-## Comments
-
-If you have any problems or suggestions, feel free to put it up in this [Issue][issue#1].
-
-Or you may also use [![Gitter][gitter-picture]][gitter]
-
-[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
-[py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
-[py35]: https://img.shields.io/badge/python-3.5-red.svg
-[chinese-version]: https://github.com/littlecodersh/ItChat/blob/master/README.md
-[document]: https://itchat.readthedocs.org/zh/latest/
-[robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
-[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
-[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
From a6544ee4a0b95b4cc754e0275c8a75c22d5c8aca Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:14:02 +0800
Subject: [PATCH 06/13] Delete __init__.py
---
itchat/components/__init__.py | 12 ------------
1 file changed, 12 deletions(-)
delete mode 100644 itchat/components/__init__.py
diff --git a/itchat/components/__init__.py b/itchat/components/__init__.py
deleted file mode 100644
index f088c173..00000000
--- a/itchat/components/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from .contact import load_contact
-from .hotreload import load_hotreload
-from .login import load_login
-from .messages import load_messages
-from .register import load_register
-
-def load_components(core):
- load_contact(core)
- load_hotreload(core)
- load_login(core)
- load_messages(core)
- load_register(core)
From 0e5325df6e7e5b25c460efd8db2a639e580d044e Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:14:43 +0800
Subject: [PATCH 07/13] Delete login.py
---
itchat/components/login.py | 361 -------------------------------------
1 file changed, 361 deletions(-)
delete mode 100644 itchat/components/login.py
diff --git a/itchat/components/login.py b/itchat/components/login.py
deleted file mode 100644
index c1fb0391..00000000
--- a/itchat/components/login.py
+++ /dev/null
@@ -1,361 +0,0 @@
-import os, time, re, io
-import threading
-import json, xml.dom.minidom
-import random
-import traceback, logging
-try:
- from httplib import BadStatusLine
-except ImportError:
- from http.client import BadStatusLine
-
-import requests
-from pyqrcode import QRCode
-
-from .. import config, utils
-from ..returnvalues import ReturnValue
-from ..storage.templates import wrap_user_dict
-from .contact import update_local_chatrooms, update_local_friends
-from .messages import produce_msg
-
-logger = logging.getLogger('itchat')
-
-def load_login(core):
- core.login = login
- core.get_QRuuid = get_QRuuid
- core.get_QR = get_QR
- core.check_login = check_login
- core.web_init = web_init
- core.show_mobile_login = show_mobile_login
- core.start_receiving = start_receiving
- core.get_msg = get_msg
- core.logout = logout
-
-def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
- loginCallback=None, exitCallback=None):
- if self.alive or self.isLogging:
- logger.warning('itchat has already logged in.')
- return
- self.isLogging = True
- while self.isLogging:
- uuid = push_login(self)
- if uuid:
- qrStorage = io.BytesIO()
- else:
- logger.info('Getting uuid of QR code.')
- while not self.get_QRuuid():
- time.sleep(1)
- logger.info('Downloading QR code.')
- qrStorage = self.get_QR(enableCmdQR=enableCmdQR,
- picDir=picDir, qrCallback=qrCallback)
- logger.info('Please scan the QR code to log in.')
- isLoggedIn = False
- while not isLoggedIn:
- status = self.check_login()
- if hasattr(qrCallback, '__call__'):
- qrCallback(uuid=self.uuid, status=status, qrcode=qrStorage.getvalue())
- if status == '200':
- isLoggedIn = True
- elif status == '201':
- if isLoggedIn is not None:
- logger.info('Please press confirm on your phone.')
- isLoggedIn = None
- elif status != '408':
- break
- if isLoggedIn:
- break
- 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.')
- self.web_init()
- self.show_mobile_login()
- self.get_contact(True)
- if hasattr(loginCallback, '__call__'):
- r = loginCallback()
- else:
- utils.clear_screen()
- if os.path.exists(picDir or config.DEFAULT_QR):
- os.remove(picDir or config.DEFAULT_QR)
- logger.info('Login successfully as %s' % self.storageClass.nickName)
- self.start_receiving(exitCallback)
- self.isLogging = False
-
-def push_login(core):
- cookiesDict = core.s.cookies.get_dict()
- if 'wxuin' in cookiesDict:
- url = '%s/cgi-bin/mmwebwx-bin/webwxpushloginurl?uin=%s' % (
- config.BASE_URL, cookiesDict['wxuin'])
- headers = { 'User-Agent' : config.USER_AGENT }
- r = core.s.get(url, headers=headers).json()
- if 'uuid' in r and r.get('ret') in (0, '0'):
- core.uuid = r['uuid']
- return r['uuid']
- return False
-
-def get_QRuuid(self):
- url = '%s/jslogin' % config.BASE_URL
- params = {
- 'appid' : 'wx782c26e4c19acffb',
- 'fun' : 'new', }
- headers = { 'User-Agent' : config.USER_AGENT }
- r = self.s.get(url, params=params, headers=headers)
- regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";'
- data = re.search(regx, r.text)
- if data and data.group(1) == '200':
- self.uuid = data.group(2)
- return self.uuid
-
-def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
- uuid = uuid or self.uuid
- picDir = picDir or config.DEFAULT_QR
- qrStorage = io.BytesIO()
- qrCode = QRCode('https://login.weixin.qq.com/l/' + uuid)
- qrCode.png(qrStorage, scale=10)
- 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:
- utils.print_qr(picDir)
- return qrStorage
-
-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=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':
- if process_login_info(self, r.text):
- return '200'
- else:
- return '400'
- elif data:
- return data.group(1)
- else:
- return '400'
-
-def process_login_info(core, loginContent):
- ''' when finish login (scanning qrcode)
- * syncUrl and fileUploadingUrl will be fetched
- * deviceid and msgid will be generated
- * skey, wxsid, wxuin, pass_ticket will be fetched
- '''
- regx = r'window.redirect_uri="(\S+)";'
- core.loginInfo['url'] = re.search(regx, loginContent).group(1)
- headers = { 'User-Agent' : config.USER_AGENT }
- r = core.s.get(core.loginInfo['url'], headers=headers, allow_redirects=False)
- core.loginInfo['url'] = core.loginInfo['url'][:core.loginInfo['url'].rfind('/')]
- for indexUrl, detailedUrl in (
- ("wx2.qq.com" , ("file.wx2.qq.com", "webpush.wx2.qq.com")),
- ("wx8.qq.com" , ("file.wx8.qq.com", "webpush.wx8.qq.com")),
- ("qq.com" , ("file.wx.qq.com", "webpush.wx.qq.com")),
- ("web2.wechat.com" , ("file.web2.wechat.com", "webpush.web2.wechat.com")),
- ("wechat.com" , ("file.web.wechat.com", "webpush.web.wechat.com"))):
- fileUrl, syncUrl = ['https://%s/cgi-bin/mmwebwx-bin' % url for url in detailedUrl]
- if indexUrl in core.loginInfo['url']:
- core.loginInfo['fileUrl'], core.loginInfo['syncUrl'] = \
- fileUrl, syncUrl
- break
- 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':
- core.loginInfo['skey'] = core.loginInfo['BaseRequest']['Skey'] = node.childNodes[0].data
- elif node.nodeName == 'wxsid':
- core.loginInfo['wxsid'] = core.loginInfo['BaseRequest']['Sid'] = node.childNodes[0].data
- elif node.nodeName == 'wxuin':
- 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' % 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, 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')
- self.loginInfo['InviteStartCount'] = int(dic['InviteStartCount'])
- self.loginInfo['User'] = wrap_user_dict(utils.struct_friend_info(dic['User']))
- self.memberList.append(self.loginInfo['User'])
- self.loginInfo['SyncKey'] = dic['SyncKey']
- self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
- for item in dic['SyncKey']['List']])
- 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']:
- 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)
- if chatroomList:
- update_local_chatrooms(self, chatroomList)
- if otherList:
- update_local_friends(self, otherList)
- return dic
-
-def show_mobile_login(self):
- url = '%s/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (
- self.loginInfo['url'], self.loginInfo['pass_ticket'])
- data = {
- 'BaseRequest' : self.loginInfo['BaseRequest'],
- 'Code' : 3,
- 'FromUserName' : self.storageClass.userName,
- 'ToUserName' : self.storageClass.userName,
- 'ClientMsgId' : int(time.time()), }
- headers = {
- 'ContentType': 'application/json; charset=UTF-8',
- 'User-Agent' : config.USER_AGENT, }
- r = self.s.post(url, data=json.dumps(data), headers=headers)
- return ReturnValue(rawResponse=r)
-
-def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
- self.alive = True
- def maintain_loop():
- retryCount = 0
- while self.alive:
- try:
- i = sync_check(self)
- if i is None:
- self.alive = False
- elif i == '0':
- pass
- else:
- msgList, contactList = self.get_msg()
- if msgList:
- msgList = produce_msg(self, msgList)
- for msg in msgList:
- self.msgList.put(msg)
- if contactList:
- chatroomList, otherList = [], []
- for contact in contactList:
- if '@@' in contact['UserName']:
- chatroomList.append(contact)
- else:
- otherList.append(contact)
- chatroomMsg = update_local_chatrooms(self, chatroomList)
- chatroomMsg['User'] = self.loginInfo['User']
- self.msgList.put(chatroomMsg)
- update_local_friends(self, otherList)
- retryCount = 0
- except requests.exceptions.ReadTimeout:
- pass
- except:
- retryCount += 1
- logger.error(traceback.format_exc())
- if self.receivingRetryCount < retryCount:
- self.alive = False
- else:
- time.sleep(1)
- self.logout()
- if hasattr(exitCallback, '__call__'):
- exitCallback()
- else:
- logger.info('LOG OUT!')
- if getReceivingFnOnly:
- return maintain_loop
- else:
- maintainThread = threading.Thread(target=maintain_loop)
- maintainThread.setDaemon(True)
- maintainThread.start()
-
-def sync_check(self):
- url = '%s/synccheck' % self.loginInfo.get('syncUrl', self.loginInfo['url'])
- params = {
- 'r' : int(time.time() * 1000),
- 'skey' : self.loginInfo['skey'],
- 'sid' : self.loginInfo['wxsid'],
- 'uin' : self.loginInfo['wxuin'],
- 'deviceid' : self.loginInfo['deviceid'],
- 'synckey' : self.loginInfo['synckey'],
- '_' : self.loginInfo['logintime'], }
- headers = { 'User-Agent' : config.USER_AGENT }
- 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':
- logger.debug('Unexpected sync check result: %s' % r.text)
- return None
- return pm.group(2)
-
-def get_msg(self):
- url = '%s/webwxsync?sid=%s&skey=%s&pass_ticket=%s' % (
- self.loginInfo['url'], self.loginInfo['wxsid'],
- self.loginInfo['skey'],self.loginInfo['pass_ticket'])
- data = {
- 'BaseRequest' : self.loginInfo['BaseRequest'],
- 'SyncKey' : self.loginInfo['SyncKey'],
- 'rr' : ~int(time.time()), }
- headers = {
- 'ContentType': 'application/json; charset=UTF-8',
- 'User-Agent' : config.USER_AGENT }
- 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['SyncKey']
- self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
- for item in dic['SyncCheckKey']['List']])
- return dic['AddMsgList'], dic['ModContactList']
-
-def logout(self):
- if self.alive:
- url = '%s/webwxlogout' % self.loginInfo['url']
- params = {
- 'redirect' : 1,
- 'type' : 1,
- 'skey' : self.loginInfo['skey'], }
- headers = { 'User-Agent' : config.USER_AGENT }
- self.s.get(url, params=params, headers=headers)
- self.alive = False
- self.isLogging = False
- self.s.cookies.clear()
- del self.chatroomList[:]
- del self.memberList[:]
- del self.mpList[:]
- return ReturnValue({'BaseResponse': {
- 'ErrMsg': 'logout successfully.',
- 'Ret': 0, }})
From 3c0c11a3b2891de19d6b7e97e765297000390d48 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:14:51 +0800
Subject: [PATCH 08/13] Delete register.py
---
itchat/components/register.py | 103 ----------------------------------
1 file changed, 103 deletions(-)
delete mode 100644 itchat/components/register.py
diff --git a/itchat/components/register.py b/itchat/components/register.py
deleted file mode 100644
index 079d3191..00000000
--- a/itchat/components/register.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import logging, traceback, sys, threading
-try:
- import Queue
-except ImportError:
- import queue as Queue
-
-from ..log import set_logging
-from ..utils import test_connect
-from ..storage import templates
-
-logger = logging.getLogger('itchat')
-
-def load_register(core):
- core.auto_login = auto_login
- core.configured_reply = configured_reply
- core.msg_register = msg_register
- core.run = run
-
-def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
- enableCmdQR=False, picDir=None, qrCallback=None,
- loginCallback=None, exitCallback=None):
- if not test_connect():
- 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):
- return
- self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
- loginCallback=loginCallback, exitCallback=exitCallback)
- self.dump_login_status(statusStorageDir)
- else:
- self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
- loginCallback=loginCallback, exitCallback=exitCallback)
-
-def configured_reply(self):
- ''' determine the type of message and reply if its method is defined
- however, I use a strange way to determine whether a msg is from massive platform
- I haven't found a better solution here
- The main problem I'm worrying about is the mismatching of new friends added on phone
- If you have any good idea, pleeeease report an issue. I will be more than grateful.
- '''
- try:
- msg = self.msgList.get(timeout=1)
- except Queue.Empty:
- pass
- else:
- if isinstance(msg['User'], templates.User):
- replyFn = self.functionDict['FriendChat'].get(msg['Type'])
- elif isinstance(msg['User'], templates.MassivePlatform):
- replyFn = self.functionDict['MpChat'].get(msg['Type'])
- elif isinstance(msg['User'], templates.Chatroom):
- replyFn = self.functionDict['GroupChat'].get(msg['Type'])
- if replyFn is None:
- r = None
- else:
- try:
- r = replyFn(msg)
- if r is not None:
- self.send(r, msg.get('FromUserName'))
- except:
- logger.warning(traceback.format_exc())
-
-def msg_register(self, msgType, isFriendChat=False, isGroupChat=False, isMpChat=False):
- ''' a decorator constructor
- return a specific decorator based on information given '''
- if not (isinstance(msgType, list) or isinstance(msgType, tuple)):
- msgType = [msgType]
- def _msg_register(fn):
- for _msgType in msgType:
- if isFriendChat:
- self.functionDict['FriendChat'][_msgType] = fn
- if isGroupChat:
- self.functionDict['GroupChat'][_msgType] = fn
- if isMpChat:
- self.functionDict['MpChat'][_msgType] = fn
- if not any((isFriendChat, isGroupChat, isMpChat)):
- self.functionDict['FriendChat'][_msgType] = fn
- return fn
- return _msg_register
-
-def run(self, debug=False, blockThread=True):
- logger.info('Start auto replying.')
- if debug:
- set_logging(loggingLevel=logging.DEBUG)
- def reply_fn():
- try:
- while self.alive:
- self.configured_reply()
- except KeyboardInterrupt:
- if self.useHotReload:
- self.dump_login_status()
- self.alive = False
- logger.debug('itchat received an ^C and exit.')
- logger.info('Bye~')
- if blockThread:
- reply_fn()
- else:
- replyThread = threading.Thread(target=reply_fn)
- replyThread.setDaemon(True)
- replyThread.start()
From 3142501f23aedb05d1e8248b8d63eeb01ffef58a Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:15:24 +0800
Subject: [PATCH 09/13] Add files via upload
---
itchat/components/__init__.py | 14 ++
itchat/components/error.py | 9 +
itchat/components/login.py | 367 ++++++++++++++++++++++++++++++++++
itchat/components/register.py | 104 ++++++++++
4 files changed, 494 insertions(+)
create mode 100644 itchat/components/__init__.py
create mode 100644 itchat/components/error.py
create mode 100644 itchat/components/login.py
create mode 100644 itchat/components/register.py
diff --git a/itchat/components/__init__.py b/itchat/components/__init__.py
new file mode 100644
index 00000000..cb17cd84
--- /dev/null
+++ b/itchat/components/__init__.py
@@ -0,0 +1,14 @@
+from .contact import load_contact
+from .hotreload import load_hotreload
+from .login import load_login
+from .messages import load_messages
+from .register import load_register
+from .error import load_error
+
+def load_components(core):
+ load_contact(core)
+ load_hotreload(core)
+ load_login(core)
+ load_messages(core)
+ load_register(core)
+ load_error(core)
diff --git a/itchat/components/error.py b/itchat/components/error.py
new file mode 100644
index 00000000..9053e7c0
--- /dev/null
+++ b/itchat/components/error.py
@@ -0,0 +1,9 @@
+def load_error(core):
+ core.error_register=error_register
+
+def error_register(self,accept_all_errors):
+ if not accept_all_errors:
+ raise NotImplementedError()
+ def register(fn):
+ self.functionDict['Error'].append(fn)
+ return register
diff --git a/itchat/components/login.py b/itchat/components/login.py
new file mode 100644
index 00000000..bc3a0371
--- /dev/null
+++ b/itchat/components/login.py
@@ -0,0 +1,367 @@
+import os, sys, time, re, io
+import threading
+import json, xml.dom.minidom
+import copy, pickle, random
+import traceback, logging
+try:
+ from httplib import BadStatusLine
+except ImportError:
+ from http.client import BadStatusLine
+
+import requests
+from pyqrcode import QRCode
+
+from .. import config, utils
+from ..returnvalues import ReturnValue
+from ..storage.templates import wrap_user_dict
+from .contact import update_local_chatrooms, update_local_friends
+from .messages import produce_msg
+
+logger = logging.getLogger('itchat')
+requests.adapters.DEFAULT_RETRIES = 10
+
+def load_login(core):
+ core.login = login
+ core.get_QRuuid = get_QRuuid
+ core.get_QR = get_QR
+ core.check_login = check_login
+ core.web_init = web_init
+ core.show_mobile_login = show_mobile_login
+ core.start_receiving = start_receiving
+ core.get_msg = get_msg
+ core.logout = logout
+
+def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
+ loginCallback=None, exitCallback=None):
+ if self.alive or self.isLogging:
+ logger.warning('itchat has already logged in.')
+ return
+ self.isLogging = True
+ while self.isLogging:
+ uuid = push_login(self)
+ if uuid:
+ qrStorage = io.BytesIO()
+ else:
+ logger.info('Getting uuid of QR code.')
+ while not self.get_QRuuid():
+ time.sleep(1)
+ logger.info('Downloading QR code.')
+ qrStorage = self.get_QR(enableCmdQR=enableCmdQR,
+ picDir=picDir, qrCallback=qrCallback)
+ logger.info('Please scan the QR code to log in.')
+ isLoggedIn = False
+ while not isLoggedIn:
+ status = self.check_login()
+ if hasattr(qrCallback, '__call__'):
+ qrCallback(uuid=self.uuid, status=status, qrcode=qrStorage.getvalue())
+ if status == '200':
+ isLoggedIn = True
+ elif status == '201':
+ if isLoggedIn is not None:
+ logger.info('Please press confirm on your phone.')
+ isLoggedIn = None
+ elif status != '408':
+ break
+ if isLoggedIn:
+ break
+ 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.')
+ self.web_init()
+ self.show_mobile_login()
+ self.get_contact(True)
+ if hasattr(loginCallback, '__call__'):
+ r = loginCallback()
+ else:
+ utils.clear_screen()
+ if os.path.exists(picDir or config.DEFAULT_QR):
+ os.remove(picDir or config.DEFAULT_QR)
+ logger.info('Login successfully as %s' % self.storageClass.nickName)
+ self.start_receiving(exitCallback)
+ self.isLogging = False
+
+def push_login(core):
+ cookiesDict = core.s.cookies.get_dict()
+ if 'wxuin' in cookiesDict:
+ url = '%s/cgi-bin/mmwebwx-bin/webwxpushloginurl?uin=%s' % (
+ config.BASE_URL, cookiesDict['wxuin'])
+ headers = { 'User-Agent' : config.USER_AGENT }
+ r = core.s.get(url, headers=headers).json()
+ if 'uuid' in r and r.get('ret') in (0, '0'):
+ core.uuid = r['uuid']
+ return r['uuid']
+ return False
+
+def get_QRuuid(self):
+ url = '%s/jslogin' % config.BASE_URL
+ params = {
+ 'appid' : 'wx782c26e4c19acffb',
+ 'fun' : 'new', }
+ headers = { 'User-Agent' : config.USER_AGENT }
+ r = self.s.get(url, params=params, headers=headers)
+ regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";'
+ data = re.search(regx, r.text)
+ if data and data.group(1) == '200':
+ self.uuid = data.group(2)
+ return self.uuid
+
+def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
+ uuid = uuid or self.uuid
+ picDir = picDir or config.DEFAULT_QR
+ qrStorage = io.BytesIO()
+ qrCode = QRCode('https://login.weixin.qq.com/l/' + uuid)
+ qrCode.png(qrStorage, scale=10)
+ if hasattr(qrCallback, '__call__'):
+ qrCallback(uuid=uuid, status='0', qrcode=qrStorage.getvalue())
+ else:
+ 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
+
+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=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':
+ if process_login_info(self, r.text):
+ return '200'
+ else:
+ return '400'
+ elif data:
+ return data.group(1)
+ else:
+ return '400'
+
+def process_login_info(core, loginContent):
+ ''' when finish login (scanning qrcode)
+ * syncUrl and fileUploadingUrl will be fetched
+ * deviceid and msgid will be generated
+ * skey, wxsid, wxuin, pass_ticket will be fetched
+ '''
+ regx = r'window.redirect_uri="(\S+)";'
+ core.loginInfo['url'] = re.search(regx, loginContent).group(1)
+ headers = { 'User-Agent' : config.USER_AGENT }
+ r = core.s.get(core.loginInfo['url'], headers=headers, allow_redirects=False)
+ core.loginInfo['url'] = core.loginInfo['url'][:core.loginInfo['url'].rfind('/')]
+ for indexUrl, detailedUrl in (
+ ("wx2.qq.com" , ("file.wx2.qq.com", "webpush.wx2.qq.com")),
+ ("wx8.qq.com" , ("file.wx8.qq.com", "webpush.wx8.qq.com")),
+ ("qq.com" , ("file.wx.qq.com", "webpush.wx.qq.com")),
+ ("web2.wechat.com" , ("file.web2.wechat.com", "webpush.web2.wechat.com")),
+ ("wechat.com" , ("file.web.wechat.com", "webpush.web.wechat.com"))):
+ fileUrl, syncUrl = ['https://%s/cgi-bin/mmwebwx-bin' % url for url in detailedUrl]
+ if indexUrl in core.loginInfo['url']:
+ core.loginInfo['fileUrl'], core.loginInfo['syncUrl'] = \
+ fileUrl, syncUrl
+ break
+ else:
+ core.loginInfo['fileUrl'] = core.loginInfo['syncUrl'] = core.loginInfo['url']
+ core.loginInfo['deviceid'] = 'e' + repr(random.random())[2:17]
+ core.loginInfo['BaseRequest'] = {}
+ for node in xml.dom.minidom.parseString(r.text).documentElement.childNodes:
+ if node.nodeName == 'skey':
+ core.loginInfo['skey'] = core.loginInfo['BaseRequest']['Skey'] = node.childNodes[0].data
+ elif node.nodeName == 'wxsid':
+ core.loginInfo['wxsid'] = core.loginInfo['BaseRequest']['Sid'] = node.childNodes[0].data
+ elif node.nodeName == 'wxuin':
+ 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' % 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, 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')
+ self.loginInfo['InviteStartCount'] = int(dic['InviteStartCount'])
+ self.loginInfo['User'] = wrap_user_dict(utils.struct_friend_info(dic['User']))
+ self.memberList.append(self.loginInfo['User'])
+ self.loginInfo['SyncKey'] = dic['SyncKey']
+ self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
+ for item in dic['SyncKey']['List']])
+ 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']:
+ 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)
+ if chatroomList:
+ update_local_chatrooms(self, chatroomList)
+ if otherList:
+ update_local_friends(self, otherList)
+ return dic
+
+def show_mobile_login(self):
+ url = '%s/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (
+ self.loginInfo['url'], self.loginInfo['pass_ticket'])
+ data = {
+ 'BaseRequest' : self.loginInfo['BaseRequest'],
+ 'Code' : 3,
+ 'FromUserName' : self.storageClass.userName,
+ 'ToUserName' : self.storageClass.userName,
+ 'ClientMsgId' : int(time.time()), }
+ headers = {
+ 'ContentType': 'application/json; charset=UTF-8',
+ 'User-Agent' : config.USER_AGENT, }
+ r = self.s.post(url, data=json.dumps(data), headers=headers)
+ return ReturnValue(rawResponse=r)
+
+def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
+ self.alive = True
+ def maintain_loop():
+ retryCount = 0
+ while self.alive:
+ try:
+ i = sync_check(self)
+ if i is None:
+ self.alive = False
+ elif i == '0':
+ pass
+ else:
+ msgList, contactList = self.get_msg()
+ if msgList:
+ msgList = produce_msg(self, msgList)
+ for msg in msgList:
+ self.msgList.put(msg)
+ if contactList:
+ chatroomList, otherList = [], []
+ for contact in contactList:
+ if '@@' in contact['UserName']:
+ chatroomList.append(contact)
+ else:
+ otherList.append(contact)
+ chatroomMsg = update_local_chatrooms(self, chatroomList)
+ chatroomMsg['User'] = self.loginInfo['User']
+ self.msgList.put(chatroomMsg)
+ update_local_friends(self, otherList)
+ retryCount = 0
+ except requests.exceptions.ReadTimeout:
+ pass
+ except :
+ retryCount += 1
+ err=traceback.format_exc()
+ for f in self.functionDict['Error']:
+ f('Unknown Internet Error',err)
+ if self.receivingRetryCount < retryCount:
+ #self.alive = False
+ self.functionDict['Error']('Retry times reached limit','still retrying')
+ else:
+ time.sleep(1)
+ self.logout()
+ if hasattr(exitCallback, '__call__'):
+ exitCallback()
+ else:
+ logger.info('LOG OUT!')
+ if getReceivingFnOnly:
+ return maintain_loop
+ else:
+ maintainThread = threading.Thread(target=maintain_loop)
+ maintainThread.setDaemon(True)
+ maintainThread.start()
+
+def sync_check(self):
+ url = '%s/synccheck' % self.loginInfo.get('syncUrl', self.loginInfo['url'])
+ params = {
+ 'r' : int(time.time() * 1000),
+ 'skey' : self.loginInfo['skey'],
+ 'sid' : self.loginInfo['wxsid'],
+ 'uin' : self.loginInfo['wxuin'],
+ 'deviceid' : self.loginInfo['deviceid'],
+ 'synckey' : self.loginInfo['synckey'],
+ '_' : int(time.time() * 1000),}
+ headers = { 'User-Agent' : config.USER_AGENT }
+ 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):
+ for f in self.functionDict['Error']:
+ f(e)
+ return
+ # 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:
+ for f in self.functionDict['Error']:
+ f(None)
+ return
+ 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':
+ logger.debug('Unexpected sync check result: %s' % r.text)
+ return None
+ return pm.group(2)
+
+def get_msg(self):
+ url = '%s/webwxsync?sid=%s&skey=%s&pass_ticket=%s' % (
+ self.loginInfo['url'], self.loginInfo['wxsid'],
+ self.loginInfo['skey'],self.loginInfo['pass_ticket'])
+ data = {
+ 'BaseRequest' : self.loginInfo['BaseRequest'],
+ 'SyncKey' : self.loginInfo['SyncKey'],
+ 'rr' : ~int(time.time()), }
+ headers = {
+ 'ContentType': 'application/json; charset=UTF-8',
+ 'User-Agent' : config.USER_AGENT }
+ 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'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
+ for item in dic['SyncCheckKey']['List']])
+ return dic['AddMsgList'], dic['ModContactList']
+
+def logout(self):
+ if self.alive:
+ url = '%s/webwxlogout' % self.loginInfo['url']
+ params = {
+ 'redirect' : 1,
+ 'type' : 1,
+ 'skey' : self.loginInfo['skey'], }
+ headers = { 'User-Agent' : config.USER_AGENT }
+ self.s.get(url, params=params, headers=headers)
+ self.alive = False
+ self.isLogging = False
+ self.s.cookies.clear()
+ del self.chatroomList[:]
+ del self.memberList[:]
+ del self.mpList[:]
+ return ReturnValue({'BaseResponse': {
+ 'ErrMsg': 'logout successfully.',
+ 'Ret': 0, }})
diff --git a/itchat/components/register.py b/itchat/components/register.py
new file mode 100644
index 00000000..b497fb72
--- /dev/null
+++ b/itchat/components/register.py
@@ -0,0 +1,104 @@
+import logging, traceback, sys, threading
+try:
+ import Queue
+except ImportError:
+ import queue as Queue
+
+from ..log import set_logging
+from ..utils import test_connect
+from ..storage import templates
+
+logger = logging.getLogger('itchat')
+
+def load_register(core):
+ core.auto_login = auto_login
+ core.configured_reply = configured_reply
+ core.msg_register = msg_register
+ core.run = run
+
+def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
+ enableCmdQR=False, picDir=None, qrCallback=None,
+ loginCallback=None, exitCallback=None):
+ x=test_connect()
+ if x!=True:
+ for f in self.functionDict['Error']:
+ f("You can't get access to wechat. ",x)
+ self.useHotReload = hotReload
+ self.hotReloadDir = statusStorageDir
+ if hotReload:
+ if self.load_login_status(statusStorageDir,
+ loginCallback=loginCallback, exitCallback=exitCallback):
+ return
+ self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
+ loginCallback=loginCallback, exitCallback=exitCallback)
+ self.dump_login_status(statusStorageDir)
+ else:
+ self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
+ loginCallback=loginCallback, exitCallback=exitCallback)
+
+def configured_reply(self):
+ ''' determine the type of message and reply if its method is defined
+ however, I use a strange way to determine whether a msg is from massive platform
+ I haven't found a better solution here
+ The main problem I'm worrying about is the mismatching of new friends added on phone
+ If you have any good idea, pleeeease report an issue. I will be more than grateful.
+ '''
+ try:
+ msg = self.msgList.get(timeout=1)
+ except Queue.Empty:
+ pass
+ else:
+ if isinstance(msg['User'], templates.User):
+ replyFn = self.functionDict['FriendChat'].get(msg['Type'])
+ elif isinstance(msg['User'], templates.MassivePlatform):
+ replyFn = self.functionDict['MpChat'].get(msg['Type'])
+ elif isinstance(msg['User'], templates.Chatroom):
+ replyFn = self.functionDict['GroupChat'].get(msg['Type'])
+ if replyFn is None:
+ r = None
+ else:
+ try:
+ r = replyFn(msg)
+ if r is not None:
+ self.send(r, msg.get('FromUserName'))
+ except:
+ logger.warning(traceback.format_exc())
+
+def msg_register(self, msgType, isFriendChat=False, isGroupChat=False, isMpChat=False):
+ ''' a decorator constructor
+ return a specific decorator based on information given '''
+ if not (isinstance(msgType, list) or isinstance(msgType, tuple)):
+ msgType = [msgType]
+ def _msg_register(fn):
+ for _msgType in msgType:
+ if isFriendChat:
+ self.functionDict['FriendChat'][_msgType] = fn
+ if isGroupChat:
+ self.functionDict['GroupChat'][_msgType] = fn
+ if isMpChat:
+ self.functionDict['MpChat'][_msgType] = fn
+ if not any((isFriendChat, isGroupChat, isMpChat)):
+ self.functionDict['FriendChat'][_msgType] = fn
+ return fn
+ return _msg_register
+
+def run(self, debug=False, blockThread=True):
+ logger.info('Start auto replying.')
+ if debug:
+ set_logging(loggingLevel=logging.DEBUG)
+ def reply_fn():
+ try:
+ while self.alive:
+ self.configured_reply()
+ except KeyboardInterrupt:
+ if self.useHotReload:
+ self.dump_login_status()
+ self.alive = False
+ logger.debug('itchat received an ^C and exit.')
+ logger.info('Bye~')
+ if blockThread:
+ reply_fn()
+ else:
+ replyThread = threading.Thread(target=reply_fn)
+ replyThread.setDaemon(True)
+ replyThread.start()
From 31d071d10155e8ab41a7a21e070716df8cde7747 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:19:40 +0800
Subject: [PATCH 10/13] Delete __init__.py
---
itchat/__init__.py | 69 ----------------------------------------------
1 file changed, 69 deletions(-)
delete mode 100644 itchat/__init__.py
diff --git a/itchat/__init__.py b/itchat/__init__.py
deleted file mode 100644
index a7cb466b..00000000
--- a/itchat/__init__.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from . import content
-from .core import Core
-from .config import VERSION
-from .log import set_logging
-
-__version__ = VERSION
-
-instanceList = []
-
-def new_instance():
- newInstance = Core()
- instanceList.append(newInstance)
- return newInstance
-
-originInstance = new_instance()
-
-# I really want to use sys.modules[__name__] = originInstance
-# but it makes auto-fill a real mess, so forgive me for my following **
-# actually it toke me less than 30 seconds, god bless Uganda
-
-# components.login
-login = originInstance.login
-get_QRuuid = originInstance.get_QRuuid
-get_QR = originInstance.get_QR
-check_login = originInstance.check_login
-web_init = originInstance.web_init
-show_mobile_login = originInstance.show_mobile_login
-start_receiving = originInstance.start_receiving
-get_msg = originInstance.get_msg
-logout = originInstance.logout
-# components.contact
-update_chatroom = originInstance.update_chatroom
-update_friend = originInstance.update_friend
-get_contact = originInstance.get_contact
-get_friends = originInstance.get_friends
-get_chatrooms = originInstance.get_chatrooms
-get_mps = originInstance.get_mps
-set_alias = originInstance.set_alias
-set_pinned = originInstance.set_pinned
-add_friend = originInstance.add_friend
-get_head_img = originInstance.get_head_img
-create_chatroom = originInstance.create_chatroom
-set_chatroom_name = originInstance.set_chatroom_name
-delete_member_from_chatroom = originInstance.delete_member_from_chatroom
-add_member_into_chatroom = originInstance.add_member_into_chatroom
-# components.messages
-send_raw_msg = originInstance.send_raw_msg
-send_msg = originInstance.send_msg
-upload_file = originInstance.upload_file
-send_file = originInstance.send_file
-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
-# components.register
-auto_login = originInstance.auto_login
-configured_reply = originInstance.configured_reply
-msg_register = originInstance.msg_register
-error_register = originInstance.error_register
-run = originInstance.run
-# other functions
-search_friends = originInstance.search_friends
-search_chatrooms = originInstance.search_chatrooms
-search_mps = originInstance.search_mps
-set_logging = set_logging
-
From c5a62eab646ee49429f6abdf4684af346adb6392 Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:19:51 +0800
Subject: [PATCH 11/13] Delete core.py
---
itchat/core.py | 459 -------------------------------------------------
1 file changed, 459 deletions(-)
delete mode 100644 itchat/core.py
diff --git a/itchat/core.py b/itchat/core.py
deleted file mode 100644
index 52d6ae4f..00000000
--- a/itchat/core.py
+++ /dev/null
@@ -1,459 +0,0 @@
-import requests
-
-from . import storage
-from .components import load_components
-
-class Core(object):
- def __init__(self):
- ''' init is the only method defined in core.py
- alive is value showing whether core is running
- - you should call logout method to change it
- - after logout, a core object can login again
- storageClass only uses basic python types
- - so for advanced uses, inherit it yourself
- receivingRetryCount is for receiving loop retry
- - it's 5 now, but actually even 1 is enough
- - failing is failing
- '''
- self.alive, self.isLogging = False, False
- self.storageClass = storage.Storage(self)
- self.memberList = self.storageClass.memberList
- self.mpList = self.storageClass.mpList
- self.chatroomList = self.storageClass.chatroomList
- self.msgList = self.storageClass.msgList
- self.loginInfo = {}
- self.s = requests.Session()
- self.uuid = None
- self.functionDict = {'FriendChat': {}, 'GroupChat': {}, 'MpChat': {}}
- self.useHotReload, self.hotReloadDir = False, 'itchat.pkl'
- self.receivingRetryCount = 5
- def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
- loginCallback=None, exitCallback=None):
- ''' log in like web wechat does
- for log in
- - a QR code will be downloaded and opened
- - then scanning status is logged, it paused for you confirm
- - finally it logged in and show your nickName
- for options
- - enableCmdQR: show qrcode in command line
- - integers can be used to fit strange char length
- - picDir: place for storing qrcode
- - qrCallback: method that should accept uuid, status, qrcode
- - loginCallback: callback after successfully logged in
- - if not set, screen is cleared and qrcode is deleted
- - exitCallback: callback after logged out
- - it contains calling of logout
- for usage
- ..code::python
-
- import itchat
- itchat.login()
-
- it is defined in components/login.py
- and of course every single move in login can be called outside
- - you may scan source code to see how
- - and modified according to your own demand
- '''
- raise NotImplementedError()
- def get_QRuuid(self):
- ''' get uuid for qrcode
- uuid is the symbol of qrcode
- - for logging in, you need to get a uuid first
- - for downloading qrcode, you need to pass uuid to it
- - for checking login status, uuid is also required
- if uuid has timed out, just get another
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
- ''' download and show qrcode
- for options
- - uuid: if uuid is not set, latest uuid you fetched will be used
- - enableCmdQR: show qrcode in cmd
- - picDir: where to store qrcode
- - qrCallback: method that should accept uuid, status, qrcode
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def check_login(self, uuid=None):
- ''' check login status
- for options:
- - uuid: if uuid is not set, latest uuid you fetched will be used
- for return values:
- - a string will be returned
- - for meaning of return values
- - 200: log in successfully
- - 201: waiting for press confirm
- - 408: uuid timed out
- - 0 : unknown error
- for processing:
- - syncUrl and fileUrl is set
- - BaseRequest is set
- blocks until reaches any of above status
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def web_init(self):
- ''' get info necessary for initializing
- for processing:
- - own account info is set
- - inviteStartCount is set
- - syncKey is set
- - part of contact is fetched
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def show_mobile_login(self):
- ''' show web wechat login sign
- the sign is on the top of mobile phone wechat
- sign will be added after sometime even without calling this function
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
- ''' open a thread for heart loop and receiving messages
- for options:
- - exitCallback: callback after logged out
- - it contains calling of logout
- - getReceivingFnOnly: if True thread will not be created and started. Instead, receive fn will be returned.
- for processing:
- - messages: msgs are formatted and passed on to registered fns
- - contact : chatrooms are updated when related info is received
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def get_msg(self):
- ''' fetch messages
- for fetching
- - method blocks for sometime until
- - new messages are to be received
- - or anytime they like
- - synckey is updated with returned synccheckkey
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def logout(self):
- ''' logout
- if core is now alive
- logout will tell wechat backstage to logout
- and core gets ready for another login
- it is defined in components/login.py
- '''
- raise NotImplementedError()
- def update_chatroom(self, userName, detailedMember=False):
- ''' update chatroom
- for chatroom contact
- - a chatroom contact need updating to be detailed
- - detailed means members, encryid, etc
- - auto updating of heart loop is a more detailed updating
- - member uin will also be filled
- - once called, updated info will be stored
- for options
- - userName: 'UserName' key of chatroom or a list of it
- - detailedMember: whether to get members of contact
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def update_friend(self, userName):
- ''' update chatroom
- for friend contact
- - once called, updated info will be stored
- for options
- - userName: 'UserName' key of a friend or a list of it
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def get_contact(self, update=False):
- ''' fetch part of contact
- for part
- - all the massive platforms and friends are fetched
- - if update, only starred chatrooms are fetched
- for options
- - update: if not set, local value will be returned
- for results
- - chatroomList will be returned
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def get_friends(self, update=False):
- ''' fetch friends list
- for options
- - update: if not set, local value will be returned
- for results
- - a list of friends' info dicts will be returned
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def get_chatrooms(self, update=False, contactOnly=False):
- ''' fetch chatrooms list
- for options
- - update: if not set, local value will be returned
- - contactOnly: if set, only starred chatrooms will be returned
- for results
- - a list of chatrooms' info dicts will be returned
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def get_mps(self, update=False):
- ''' fetch massive platforms list
- for options
- - update: if not set, local value will be returned
- for results
- - a list of platforms' info dicts will be returned
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def set_alias(self, userName, alias):
- ''' set alias for a friend
- for options
- - userName: 'UserName' key of info dict
- - alias: new alias
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def set_pinned(self, userName, isPinned=True):
- ''' set pinned for a friend or a chatroom
- for options
- - userName: 'UserName' key of info dict
- - isPinned: whether to pin
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def add_friend(self, userName, status=2, verifyContent='', autoUpdate=True):
- ''' add a friend or accept a friend
- for options
- - userName: 'UserName' for friend's info dict
- - status:
- - for adding status should be 2
- - for accepting status should be 3
- - ticket: greeting message
- - userInfo: friend's other info for adding into local storage
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
- ''' place for docs
- for options
- - if you want to get chatroom header: only set chatroomUserName
- - if you want to get friend header: only set userName
- - if you want to get chatroom member header: set both
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def create_chatroom(self, memberList, topic=''):
- ''' create a chatroom
- for creating
- - its calling frequency is strictly limited
- for options
- - memberList: list of member info dict
- - topic: topic of new chatroom
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def set_chatroom_name(self, chatroomUserName, name):
- ''' set chatroom name
- for setting
- - it makes an updating of chatroom
- - which means detailed info will be returned in heart loop
- for options
- - chatroomUserName: 'UserName' key of chatroom info dict
- - name: new chatroom name
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def delete_member_from_chatroom(self, chatroomUserName, memberList):
- ''' deletes members from chatroom
- for deleting
- - you can't delete yourself
- - if so, no one will be deleted
- - strict-limited frequency
- for options
- - chatroomUserName: 'UserName' key of chatroom info dict
- - memberList: list of members' info dict
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def add_member_into_chatroom(self, chatroomUserName, memberList,
- useInvitation=False):
- ''' add members into chatroom
- for adding
- - you can't add yourself or member already in chatroom
- - if so, no one will be added
- - if member will over 40 after adding, invitation must be used
- - strict-limited frequency
- for options
- - chatroomUserName: 'UserName' key of chatroom info dict
- - memberList: list of members' info dict
- - useInvitation: if invitation is not required, set this to use
- it is defined in components/contact.py
- '''
- raise NotImplementedError()
- def send_raw_msg(self, msgType, content, toUserName):
- ''' many messages are sent in a common way
- for demo
- .. code:: python
-
- @itchat.msg_register(itchat.content.CARD)
- def reply(msg):
- itchat.send_raw_msg(msg['MsgType'], msg['Content'], msg['FromUserName'])
-
- there are some little tricks here, you may discover them yourself
- but remember they are tricks
- it is defined in components/messages.py
- '''
- raise NotImplementedError()
- def send_msg(self, msg='Test Message', toUserName=None):
- ''' send plain text message
- for options
- - msg: should be unicode if there's non-ascii words in msg
- - toUserName: 'UserName' key of friend dict
- it is defined in components/messages.py
- '''
- raise NotImplementedError()
- def upload_file(self, fileDir, isPicture=False, isVideo=False,
- toUserName='filehelper', file_=None, preparedFile=None):
- ''' upload file to server and get mediaId
- for options
- - fileDir: dir for file ready for upload
- - isPicture: whether file is a picture
- - isVideo: whether file is a video
- for return values
- will return a ReturnValue
- if succeeded, mediaId is in r['MediaId']
- it is defined in components/messages.py
- '''
- raise NotImplementedError()
- def send_file(self, fileDir, toUserName=None, mediaId=None, file_=None):
- ''' send attachment
- for options
- - fileDir: dir for file ready for upload
- - mediaId: mediaId for file.
- - if set, file will not be uploaded twice
- - toUserName: 'UserName' key of friend dict
- it is defined in components/messages.py
- '''
- raise NotImplementedError()
- def send_image(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
- ''' send image
- for options
- - fileDir: dir for file ready for upload
- - if it's a gif, name it like 'xx.gif'
- - mediaId: mediaId for file.
- - if set, file will not be uploaded twice
- - toUserName: 'UserName' key of friend dict
- it is defined in components/messages.py
- '''
- raise NotImplementedError()
- def send_video(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
- ''' send video
- for options
- - fileDir: dir for file ready for upload
- - if mediaId is set, it's unnecessary to set fileDir
- - mediaId: mediaId for file.
- - if set, file will not be uploaded twice
- - toUserName: 'UserName' key of friend dict
- it is defined in components/messages.py
- '''
- raise NotImplementedError()
- def send(self, msg, toUserName=None, mediaId=None):
- ''' wrapped function for all the sending functions
- for options
- - msg: message starts with different string indicates different type
- - list of type string: ['@fil@', '@img@', '@msg@', '@vid@']
- - they are for file, image, plain text, video
- - if none of them matches, it will be sent like plain text
- - toUserName: 'UserName' key of friend dict
- - mediaId: if set, uploading will not be repeated
- 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
- - fileDir: dir for dumping login status
- it is defined in components/hotreload.py
- '''
- raise NotImplementedError()
- def load_login_status(self, fileDir,
- loginCallback=None, exitCallback=None):
- ''' load login status from a specific file
- for option
- - fileDir: file for loading login status
- - loginCallback: callback after successfully logged in
- - if not set, screen is cleared and qrcode is deleted
- - exitCallback: callback after logged out
- - it contains calling of logout
- it is defined in components/hotreload.py
- '''
- raise NotImplementedError()
- def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
- enableCmdQR=False, picDir=None, qrCallback=None,
- loginCallback=None, exitCallback=None):
- ''' log in like web wechat does
- for log in
- - a QR code will be downloaded and opened
- - then scanning status is logged, it paused for you confirm
- - finally it logged in and show your nickName
- for options
- - hotReload: enable hot reload
- - statusStorageDir: dir for storing log in status
- - enableCmdQR: show qrcode in command line
- - integers can be used to fit strange char length
- - picDir: place for storing qrcode
- - loginCallback: callback after successfully logged in
- - if not set, screen is cleared and qrcode is deleted
- - exitCallback: callback after logged out
- - it contains calling of logout
- - qrCallback: method that should accept uuid, status, qrcode
- for usage
- ..code::python
-
- import itchat
- itchat.auto_login()
-
- it is defined in components/register.py
- and of course every single move in login can be called outside
- - you may scan source code to see how
- - and modified according to your own demond
- '''
- raise NotImplementedError()
- def configured_reply(self):
- ''' determine the type of message and reply if its method is defined
- however, I use a strange way to determine whether a msg is from massive platform
- I haven't found a better solution here
- The main problem I'm worrying about is the mismatching of new friends added on phone
- If you have any good idea, pleeeease report an issue. I will be more than grateful.
- '''
- raise NotImplementedError()
- def msg_register(self, msgType,
- isFriendChat=False, isGroupChat=False, isMpChat=False):
- ''' a decorator constructor
- return a specific decorator based on information given
- '''
- raise NotImplementedError()
- def run(self, debug=True, blockThread=True):
- ''' start auto respond
- for option
- - debug: if set, debug info will be shown on screen
- it is defined in components/register.py
- '''
- raise NotImplementedError()
- def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
- wechatAccount=None):
- return self.storageClass.search_friends(name, userName, remarkName,
- nickName, wechatAccount)
- def search_chatrooms(self, name=None, userName=None):
- return self.storageClass.search_chatrooms(name, userName)
- def search_mps(self, name=None, userName=None):
- return self.storageClass.search_mps(name, userName)
-
-load_components(Core)
From b157b9529efbdd0f399876c8ae061511790aa1bf Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:20:05 +0800
Subject: [PATCH 12/13] Delete utils.py
---
itchat/utils.py | 159 ------------------------------------------------
1 file changed, 159 deletions(-)
delete mode 100644 itchat/utils.py
diff --git a/itchat/utils.py b/itchat/utils.py
deleted file mode 100644
index e217368c..00000000
--- a/itchat/utils.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import re, os, sys, subprocess, copy, traceback, logging
-
-try:
- 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
-
-from . import config
-
-logger = logging.getLogger('itchat')
-
-emojiRegex = re.compile(r'')
-htmlParser = HTMLParser()
-try:
- b = u'\u2588'
- sys.stdout.write(b + '\r')
- sys.stdout.flush()
-except UnicodeEncodeError:
- BLOCK = 'MM'
-else:
- BLOCK = b
-friendInfoTemplate = {}
-for k in ('UserName', 'City', 'DisplayName', 'PYQuanPin', 'RemarkPYInitial', 'Province',
- 'KeyWord', 'RemarkName', 'PYInitial', 'EncryChatRoomId', 'Alias', 'Signature',
- 'NickName', 'RemarkPYQuanPin', 'HeadImgUrl'):
- friendInfoTemplate[k] = ''
-for k in ('UniFriend', 'Sex', 'AppAccountFlag', 'VerifyFlag', 'ChatRoomId', 'HideInputBarFlag',
- 'AttrStatus', 'SnsFlag', 'MemberCount', 'OwnerUin', 'ContactFlag', 'Uin',
- 'StarFriend', 'Statues'):
- friendInfoTemplate[k] = 0
-friendInfoTemplate['MemberList'] = []
-
-def clear_screen():
- os.system('cls' if config.OS == 'Windows' else 'clear')
-
-def emoji_formatter(d, k):
- ''' _emoji_deebugger is for bugs about emoji match caused by wechat backstage
- like :face with tears of joy: will be replaced with :cat face with tears of joy:
- '''
- def _emoji_debugger(d, k):
- s = d[k].replace('') # fix missing bug
- def __fix_miss_match(m):
- return '' % ({
- '1f63c': '1f601', '1f639': '1f602', '1f63a': '1f603',
- '1f4ab': '1f616', '1f64d': '1f614', '1f63b': '1f60d',
- '1f63d': '1f618', '1f64e': '1f621', '1f63f': '1f622',
- }.get(m.group(1), m.group(1)))
- return emojiRegex.sub(__fix_miss_match, s)
- def _emoji_formatter(m):
- s = m.group(1)
- if len(s) == 6:
- return ('\\U%s\\U%s'%(s[:2].rjust(8, '0'), s[2:].rjust(8, '0'))
- ).encode('utf8').decode('unicode-escape', 'replace')
- elif len(s) == 10:
- return ('\\U%s\\U%s'%(s[:5].rjust(8, '0'), s[5:].rjust(8, '0'))
- ).encode('utf8').decode('unicode-escape', 'replace')
- else:
- return ('\\U%s'%m.group(1).rjust(8, '0')
- ).encode('utf8').decode('unicode-escape', 'replace')
- d[k] = _emoji_debugger(d, k)
- d[k] = emojiRegex.sub(_emoji_formatter, d[k])
-
-def msg_formatter(d, k):
- emoji_formatter(d, k)
- d[k] = d[k].replace('
', '\n')
- d[k] = htmlParser.unescape(d[k])
-
-def check_file(fileDir):
- try:
- with open(fileDir):
- pass
- return True
- except:
- return False
-
-def print_qr(fileDir):
- if config.OS == 'Darwin':
- subprocess.call(['open', fileDir])
- elif config.OS == 'Linux':
- subprocess.call(['xdg-open', fileDir])
- else:
- os.startfile(fileDir)
-
-def print_cmd_qr(qrText, white=BLOCK, black=' ', enableCmdQR=True):
- blockCount = int(enableCmdQR)
- if abs(blockCount) == 0:
- blockCount = 1
- white *= abs(blockCount)
- if blockCount < 0:
- white, black = black, white
- sys.stdout.write(' '*50 + '\r')
- sys.stdout.flush()
- qr = qrText.replace('0', white).replace('1', black)
- sys.stdout.write(qr)
- sys.stdout.flush()
-
-def struct_friend_info(knownInfo):
- member = copy.deepcopy(friendInfoTemplate)
- for k, v in copy.deepcopy(knownInfo).items(): member[k] = v
- return member
-
-def search_dict_list(l, key, value):
- ''' Search a list of dict
- * return dict with specific value & key '''
- for i in l:
- if i.get(key) == value:
- return i
-
-def print_line(msg, oneLine = False):
- if oneLine:
- sys.stdout.write(' '*40 + '\r')
- sys.stdout.flush()
- else:
- sys.stdout.write('\n')
- sys.stdout.write(msg.encode(sys.stdin.encoding or 'utf8', 'replace'
- ).decode(sys.stdin.encoding or 'utf8', 'replace'))
- sys.stdout.flush()
-
-def test_connect(retryTime=5):
- for i in range(retryTime):
- try:
- r = requests.get(config.BASE_URL)
- return True
- except:
- if i == retryTime - 1:
- logger.error(traceback.format_exc())
- return False
-
-def contact_deep_copy(core, contact):
- with core.storageClass.updateLock:
- return copy.deepcopy(contact)
-
-def get_image_postfix(data):
- data = data[:20]
- if b'GIF' in data:
- return 'gif'
- elif b'PNG' in data:
- return 'png'
- elif b'JFIF' in data:
- return 'jpg'
- return ''
-
-def update_info_dict(oldInfoDict, newInfoDict):
- ''' only normal values will be updated here
- because newInfoDict is normal dict, so it's not necessary to consider templates
- '''
- for k, v in newInfoDict.items():
- if any((isinstance(v, t) for t in (tuple, list, dict))):
- pass # these values will be updated somewhere else
- elif oldInfoDict.get(k) is None or v not in (None, '', '0', 0):
- oldInfoDict[k] = v
From f1efacf2180f550064dc22b768d2847111e786dc Mon Sep 17 00:00:00 2001
From: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Date: Thu, 19 Mar 2020 17:20:30 +0800
Subject: [PATCH 13/13] Add files via upload
---
itchat/__init__.py | 69 +++++++
itchat/core.py | 461 +++++++++++++++++++++++++++++++++++++++++++++
itchat/utils.py | 153 +++++++++++++++
3 files changed, 683 insertions(+)
create mode 100644 itchat/__init__.py
create mode 100644 itchat/core.py
create mode 100644 itchat/utils.py
diff --git a/itchat/__init__.py b/itchat/__init__.py
new file mode 100644
index 00000000..a7cb466b
--- /dev/null
+++ b/itchat/__init__.py
@@ -0,0 +1,69 @@
+from . import content
+from .core import Core
+from .config import VERSION
+from .log import set_logging
+
+__version__ = VERSION
+
+instanceList = []
+
+def new_instance():
+ newInstance = Core()
+ instanceList.append(newInstance)
+ return newInstance
+
+originInstance = new_instance()
+
+# I really want to use sys.modules[__name__] = originInstance
+# but it makes auto-fill a real mess, so forgive me for my following **
+# actually it toke me less than 30 seconds, god bless Uganda
+
+# components.login
+login = originInstance.login
+get_QRuuid = originInstance.get_QRuuid
+get_QR = originInstance.get_QR
+check_login = originInstance.check_login
+web_init = originInstance.web_init
+show_mobile_login = originInstance.show_mobile_login
+start_receiving = originInstance.start_receiving
+get_msg = originInstance.get_msg
+logout = originInstance.logout
+# components.contact
+update_chatroom = originInstance.update_chatroom
+update_friend = originInstance.update_friend
+get_contact = originInstance.get_contact
+get_friends = originInstance.get_friends
+get_chatrooms = originInstance.get_chatrooms
+get_mps = originInstance.get_mps
+set_alias = originInstance.set_alias
+set_pinned = originInstance.set_pinned
+add_friend = originInstance.add_friend
+get_head_img = originInstance.get_head_img
+create_chatroom = originInstance.create_chatroom
+set_chatroom_name = originInstance.set_chatroom_name
+delete_member_from_chatroom = originInstance.delete_member_from_chatroom
+add_member_into_chatroom = originInstance.add_member_into_chatroom
+# components.messages
+send_raw_msg = originInstance.send_raw_msg
+send_msg = originInstance.send_msg
+upload_file = originInstance.upload_file
+send_file = originInstance.send_file
+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
+# components.register
+auto_login = originInstance.auto_login
+configured_reply = originInstance.configured_reply
+msg_register = originInstance.msg_register
+error_register = originInstance.error_register
+run = originInstance.run
+# other functions
+search_friends = originInstance.search_friends
+search_chatrooms = originInstance.search_chatrooms
+search_mps = originInstance.search_mps
+set_logging = set_logging
+
diff --git a/itchat/core.py b/itchat/core.py
new file mode 100644
index 00000000..837ad25b
--- /dev/null
+++ b/itchat/core.py
@@ -0,0 +1,461 @@
+import logging
+
+import requests
+
+from . import config, storage, utils, log
+from .components import load_components
+
+class Core(object):
+ def __init__(self):
+ ''' init is the only method defined in core.py
+ alive is value showing whether core is running
+ - you should call logout method to change it
+ - after logout, a core object can login again
+ storageClass only uses basic python types
+ - so for advanced uses, inherit it yourself
+ receivingRetryCount is for receiving loop retry
+ - it's 5 now, but actually even 1 is enough
+ - failing is failing
+ '''
+ self.alive, self.isLogging = False, False
+ self.storageClass = storage.Storage(self)
+ self.memberList = self.storageClass.memberList
+ self.mpList = self.storageClass.mpList
+ self.chatroomList = self.storageClass.chatroomList
+ self.msgList = self.storageClass.msgList
+ self.loginInfo = {}
+ self.s = requests.Session()
+ self.uuid = None
+ self.functionDict = {'FriendChat': {}, 'GroupChat': {}, 'MpChat': {},'Error':[]}
+ self.useHotReload, self.hotReloadDir = False, 'itchat.pkl'
+ self.receivingRetryCount = 5
+ def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
+ loginCallback=None, exitCallback=None):
+ ''' log in like web wechat does
+ for log in
+ - a QR code will be downloaded and opened
+ - then scanning status is logged, it paused for you confirm
+ - finally it logged in and show your nickName
+ for options
+ - enableCmdQR: show qrcode in command line
+ - integers can be used to fit strange char length
+ - picDir: place for storing qrcode
+ - qrCallback: method that should accept uuid, status, qrcode
+ - loginCallback: callback after successfully logged in
+ - if not set, screen is cleared and qrcode is deleted
+ - exitCallback: callback after logged out
+ - it contains calling of logout
+ for usage
+ ..code::python
+
+ import itchat
+ itchat.login()
+
+ it is defined in components/login.py
+ and of course every single move in login can be called outside
+ - you may scan source code to see how
+ - and modified according to your own demand
+ '''
+ raise NotImplementedError()
+ def get_QRuuid(self):
+ ''' get uuid for qrcode
+ uuid is the symbol of qrcode
+ - for logging in, you need to get a uuid first
+ - for downloading qrcode, you need to pass uuid to it
+ - for checking login status, uuid is also required
+ if uuid has timed out, just get another
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
+ ''' download and show qrcode
+ for options
+ - uuid: if uuid is not set, latest uuid you fetched will be used
+ - enableCmdQR: show qrcode in cmd
+ - picDir: where to store qrcode
+ - qrCallback: method that should accept uuid, status, qrcode
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def check_login(self, uuid=None):
+ ''' check login status
+ for options:
+ - uuid: if uuid is not set, latest uuid you fetched will be used
+ for return values:
+ - a string will be returned
+ - for meaning of return values
+ - 200: log in successfully
+ - 201: waiting for press confirm
+ - 408: uuid timed out
+ - 0 : unknown error
+ for processing:
+ - syncUrl and fileUrl is set
+ - BaseRequest is set
+ blocks until reaches any of above status
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def web_init(self):
+ ''' get info necessary for initializing
+ for processing:
+ - own account info is set
+ - inviteStartCount is set
+ - syncKey is set
+ - part of contact is fetched
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def show_mobile_login(self):
+ ''' show web wechat login sign
+ the sign is on the top of mobile phone wechat
+ sign will be added after sometime even without calling this function
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
+ ''' open a thread for heart loop and receiving messages
+ for options:
+ - exitCallback: callback after logged out
+ - it contains calling of logout
+ - getReceivingFnOnly: if True thread will not be created and started. Instead, receive fn will be returned.
+ for processing:
+ - messages: msgs are formatted and passed on to registered fns
+ - contact : chatrooms are updated when related info is received
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def get_msg(self):
+ ''' fetch messages
+ for fetching
+ - method blocks for sometime util
+ - new messages are to be received
+ - or anytime they like
+ - synckey is updated with returned synccheckkey
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def logout(self):
+ ''' logout
+ if core is now alive
+ logout will tell wechat backstage to logout
+ and core gets ready for another login
+ it is defined in components/login.py
+ '''
+ raise NotImplementedError()
+ def update_chatroom(self, userName, detailedMember=False):
+ ''' update chatroom
+ for chatroom contact
+ - a chatroom contact need updating to be detailed
+ - detailed means members, encryid, etc
+ - auto updating of heart loop is a more detailed updating
+ - member uin will also be filled
+ - once called, updated info will be stored
+ for options
+ - userName: 'UserName' key of chatroom or a list of it
+ - detailedMember: whether to get members of contact
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def update_friend(self, userName):
+ ''' update chatroom
+ for friend contact
+ - once called, updated info will be stored
+ for options
+ - userName: 'UserName' key of a friend or a list of it
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def get_contact(self, update=False):
+ ''' fetch part of contact
+ for part
+ - all the massive platforms and friends are fetched
+ - if update, only starred chatrooms are fetched
+ for options
+ - update: if not set, local value will be returned
+ for results
+ - chatroomList will be returned
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def get_friends(self, update=False):
+ ''' fetch friends list
+ for options
+ - update: if not set, local value will be returned
+ for results
+ - a list of friends' info dicts will be returned
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def get_chatrooms(self, update=False, contactOnly=False):
+ ''' fetch chatrooms list
+ for options
+ - update: if not set, local value will be returned
+ - contactOnly: if set, only starred chatrooms will be returned
+ for results
+ - a list of chatrooms' info dicts will be returned
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def get_mps(self, update=False):
+ ''' fetch massive platforms list
+ for options
+ - update: if not set, local value will be returned
+ for results
+ - a list of platforms' info dicts will be returned
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def set_alias(self, userName, alias):
+ ''' set alias for a friend
+ for options
+ - userName: 'UserName' key of info dict
+ - alias: new alias
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def set_pinned(self, userName, isPinned=True):
+ ''' set pinned for a friend or a chatroom
+ for options
+ - userName: 'UserName' key of info dict
+ - isPinned: whether to pin
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def add_friend(self, userName, status=2, verifyContent='', autoUpdate=True):
+ ''' add a friend or accept a friend
+ for options
+ - userName: 'UserName' for friend's info dict
+ - status:
+ - for adding status should be 2
+ - for accepting status should be 3
+ - ticket: greeting message
+ - userInfo: friend's other info for adding into local storage
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
+ ''' place for docs
+ for options
+ - if you want to get chatroom header: only set chatroomUserName
+ - if you want to get friend header: only set userName
+ - if you want to get chatroom member header: set both
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def create_chatroom(self, memberList, topic=''):
+ ''' create a chatroom
+ for creating
+ - its calling frequency is strictly limited
+ for options
+ - memberList: list of member info dict
+ - topic: topic of new chatroom
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def set_chatroom_name(self, chatroomUserName, name):
+ ''' set chatroom name
+ for setting
+ - it makes an updating of chatroom
+ - which means detailed info will be returned in heart loop
+ for options
+ - chatroomUserName: 'UserName' key of chatroom info dict
+ - name: new chatroom name
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def delete_member_from_chatroom(self, chatroomUserName, memberList):
+ ''' deletes members from chatroom
+ for deleting
+ - you can't delete yourself
+ - if so, no one will be deleted
+ - strict-limited frequency
+ for options
+ - chatroomUserName: 'UserName' key of chatroom info dict
+ - memberList: list of members' info dict
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def add_member_into_chatroom(self, chatroomUserName, memberList,
+ useInvitation=False):
+ ''' add members into chatroom
+ for adding
+ - you can't add yourself or member already in chatroom
+ - if so, no one will be added
+ - if member will over 40 after adding, invitation must be used
+ - strict-limited frequency
+ for options
+ - chatroomUserName: 'UserName' key of chatroom info dict
+ - memberList: list of members' info dict
+ - useInvitation: if invitation is not required, set this to use
+ it is defined in components/contact.py
+ '''
+ raise NotImplementedError()
+ def send_raw_msg(self, msgType, content, toUserName):
+ ''' many messages are sent in a common way
+ for demo
+ .. code:: python
+
+ @itchat.msg_register(itchat.content.CARD)
+ def reply(msg):
+ itchat.send_raw_msg(msg['MsgType'], msg['Content'], msg['FromUserName'])
+
+ there are some little tricks here, you may discover them yourself
+ but remember they are tricks
+ it is defined in components/messages.py
+ '''
+ raise NotImplementedError()
+ def send_msg(self, msg='Test Message', toUserName=None):
+ ''' send plain text message
+ for options
+ - msg: should be unicode if there's non-ascii words in msg
+ - toUserName: 'UserName' key of friend dict
+ it is defined in components/messages.py
+ '''
+ raise NotImplementedError()
+ def upload_file(self, fileDir, isPicture=False, isVideo=False,
+ toUserName='filehelper', file_=None, preparedFile=None):
+ ''' upload file to server and get mediaId
+ for options
+ - fileDir: dir for file ready for upload
+ - isPicture: whether file is a picture
+ - isVideo: whether file is a video
+ for return values
+ will return a ReturnValue
+ if succeeded, mediaId is in r['MediaId']
+ it is defined in components/messages.py
+ '''
+ raise NotImplementedError()
+ def send_file(self, fileDir, toUserName=None, mediaId=None, file_=None):
+ ''' send attachment
+ for options
+ - fileDir: dir for file ready for upload
+ - mediaId: mediaId for file.
+ - if set, file will not be uploaded twice
+ - toUserName: 'UserName' key of friend dict
+ it is defined in components/messages.py
+ '''
+ raise NotImplementedError()
+ def send_image(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
+ ''' send image
+ for options
+ - fileDir: dir for file ready for upload
+ - if it's a gif, name it like 'xx.gif'
+ - mediaId: mediaId for file.
+ - if set, file will not be uploaded twice
+ - toUserName: 'UserName' key of friend dict
+ it is defined in components/messages.py
+ '''
+ raise NotImplementedError()
+ def send_video(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
+ ''' send video
+ for options
+ - fileDir: dir for file ready for upload
+ - if mediaId is set, it's unnecessary to set fileDir
+ - mediaId: mediaId for file.
+ - if set, file will not be uploaded twice
+ - toUserName: 'UserName' key of friend dict
+ it is defined in components/messages.py
+ '''
+ raise NotImplementedError()
+ def send(self, msg, toUserName=None, mediaId=None):
+ ''' wrapped function for all the sending functions
+ for options
+ - msg: message starts with different string indicates different type
+ - list of type string: ['@fil@', '@img@', '@msg@', '@vid@']
+ - they are for file, image, plain text, video
+ - if none of them matches, it will be sent like plain text
+ - toUserName: 'UserName' key of friend dict
+ - mediaId: if set, uploading will not be repeated
+ 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
+ - fileDir: dir for dumping login status
+ it is defined in components/hotreload.py
+ '''
+ raise NotImplementedError()
+ def load_login_status(self, fileDir,
+ loginCallback=None, exitCallback=None):
+ ''' load login status from a specific file
+ for option
+ - fileDir: file for loading login status
+ - loginCallback: callback after successfully logged in
+ - if not set, screen is cleared and qrcode is deleted
+ - exitCallback: callback after logged out
+ - it contains calling of logout
+ it is defined in components/hotreload.py
+ '''
+ raise NotImplementedError()
+ def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
+ enableCmdQR=False, picDir=None, qrCallback=None,
+ loginCallback=None, exitCallback=None):
+ ''' log in like web wechat does
+ for log in
+ - a QR code will be downloaded and opened
+ - then scanning status is logged, it paused for you confirm
+ - finally it logged in and show your nickName
+ for options
+ - hotReload: enable hot reload
+ - statusStorageDir: dir for storing log in status
+ - enableCmdQR: show qrcode in command line
+ - integers can be used to fit strange char length
+ - picDir: place for storing qrcode
+ - loginCallback: callback after successfully logged in
+ - if not set, screen is cleared and qrcode is deleted
+ - exitCallback: callback after logged out
+ - it contains calling of logout
+ - qrCallback: method that should accept uuid, status, qrcode
+ for usage
+ ..code::python
+
+ import itchat
+ itchat.auto_login()
+
+ it is defined in components/register.py
+ and of course every single move in login can be called outside
+ - you may scan source code to see how
+ - and modified according to your own demond
+ '''
+ raise NotImplementedError()
+ def configured_reply(self):
+ ''' determine the type of message and reply if its method is defined
+ however, I use a strange way to determine whether a msg is from massive platform
+ I haven't found a better solution here
+ The main problem I'm worrying about is the mismatching of new friends added on phone
+ If you have any good idea, pleeeease report an issue. I will be more than grateful.
+ '''
+ raise NotImplementedError()
+ def msg_register(self, msgType,
+ isFriendChat=False, isGroupChat=False, isMpChat=False):
+ ''' a decorator constructor
+ return a specific decorator based on information given
+ '''
+ raise NotImplementedError()
+ def run(self, debug=True, blockThread=True):
+ ''' start auto respond
+ for option
+ - debug: if set, debug info will be shown on screen
+ it is defined in components/register.py
+ '''
+ raise NotImplementedError()
+ def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
+ wechatAccount=None):
+ return self.storageClass.search_friends(name, userName, remarkName,
+ nickName, wechatAccount)
+ def search_chatrooms(self, name=None, userName=None):
+ return self.storageClass.search_chatrooms(name, userName)
+ def search_mps(self, name=None, userName=None):
+ return self.storageClass.search_mps(name, userName)
+requests.adapters.DEFAULT_RETRIES = 10
+load_components(Core)
diff --git a/itchat/utils.py b/itchat/utils.py
new file mode 100644
index 00000000..8b46540e
--- /dev/null
+++ b/itchat/utils.py
@@ -0,0 +1,153 @@
+import re, os, sys, subprocess, copy, traceback, logging
+
+try:
+ from HTMLParser import HTMLParser
+except ImportError:
+ from html.parser import HTMLParser
+
+import requests
+
+from . import config
+
+logger = logging.getLogger('itchat')
+
+emojiRegex = re.compile(r'')
+htmlParser = HTMLParser()
+try:
+ b = u'\u2588'
+ sys.stdout.write(b + '\r')
+ sys.stdout.flush()
+except UnicodeEncodeError:
+ BLOCK = 'MM'
+else:
+ BLOCK = b
+friendInfoTemplate = {}
+for k in ('UserName', 'City', 'DisplayName', 'PYQuanPin', 'RemarkPYInitial', 'Province',
+ 'KeyWord', 'RemarkName', 'PYInitial', 'EncryChatRoomId', 'Alias', 'Signature',
+ 'NickName', 'RemarkPYQuanPin', 'HeadImgUrl'):
+ friendInfoTemplate[k] = ''
+for k in ('UniFriend', 'Sex', 'AppAccountFlag', 'VerifyFlag', 'ChatRoomId', 'HideInputBarFlag',
+ 'AttrStatus', 'SnsFlag', 'MemberCount', 'OwnerUin', 'ContactFlag', 'Uin',
+ 'StarFriend', 'Statues'):
+ friendInfoTemplate[k] = 0
+friendInfoTemplate['MemberList'] = []
+
+def clear_screen():
+ os.system('cls' if config.OS == 'Windows' else 'clear')
+
+def emoji_formatter(d, k):
+ ''' _emoji_deebugger is for bugs about emoji match caused by wechat backstage
+ like :face with tears of joy: will be replaced with :cat face with tears of joy:
+ '''
+ def _emoji_debugger(d, k):
+ s = d[k].replace('') # fix missing bug
+ def __fix_miss_match(m):
+ return '' % ({
+ '1f63c': '1f601', '1f639': '1f602', '1f63a': '1f603',
+ '1f4ab': '1f616', '1f64d': '1f614', '1f63b': '1f60d',
+ '1f63d': '1f618', '1f64e': '1f621', '1f63f': '1f622',
+ }.get(m.group(1), m.group(1)))
+ return emojiRegex.sub(__fix_miss_match, s)
+ def _emoji_formatter(m):
+ s = m.group(1)
+ if len(s) == 6:
+ return ('\\U%s\\U%s'%(s[:2].rjust(8, '0'), s[2:].rjust(8, '0'))
+ ).encode('utf8').decode('unicode-escape', 'replace')
+ elif len(s) == 10:
+ return ('\\U%s\\U%s'%(s[:5].rjust(8, '0'), s[5:].rjust(8, '0'))
+ ).encode('utf8').decode('unicode-escape', 'replace')
+ else:
+ return ('\\U%s'%m.group(1).rjust(8, '0')
+ ).encode('utf8').decode('unicode-escape', 'replace')
+ d[k] = _emoji_debugger(d, k)
+ d[k] = emojiRegex.sub(_emoji_formatter, d[k])
+
+def msg_formatter(d, k):
+ emoji_formatter(d, k)
+ d[k] = d[k].replace('
', '\n')
+ d[k] = htmlParser.unescape(d[k])
+
+def check_file(fileDir):
+ try:
+ with open(fileDir):
+ pass
+ return True
+ except:
+ return False
+
+def print_qr(fileDir):
+ if config.OS == 'Darwin':
+ subprocess.call(['open', fileDir])
+ elif config.OS == 'Linux':
+ subprocess.call(['xdg-open', fileDir])
+ else:
+ os.startfile(fileDir)
+
+def print_cmd_qr(qrText, white=BLOCK, black=' ', enableCmdQR=True):
+ blockCount = int(enableCmdQR)
+ if abs(blockCount) == 0:
+ blockCount = 1
+ white *= abs(blockCount)
+ if blockCount < 0:
+ white, black = black, white
+ sys.stdout.write(' '*50 + '\r')
+ sys.stdout.flush()
+ qr = qrText.replace('0', white).replace('1', black)
+ sys.stdout.write(qr)
+ sys.stdout.flush()
+
+def struct_friend_info(knownInfo):
+ member = copy.deepcopy(friendInfoTemplate)
+ for k, v in copy.deepcopy(knownInfo).items(): member[k] = v
+ return member
+
+def search_dict_list(l, key, value):
+ ''' Search a list of dict
+ * return dict with specific value & key '''
+ for i in l:
+ if i.get(key) == value:
+ return i
+
+def print_line(msg, oneLine = False):
+ if oneLine:
+ sys.stdout.write(' '*40 + '\r')
+ sys.stdout.flush()
+ else:
+ sys.stdout.write('\n')
+ sys.stdout.write(msg.encode(sys.stdin.encoding or 'utf8', 'replace'
+ ).decode(sys.stdin.encoding or 'utf8', 'replace'))
+ sys.stdout.flush()
+
+def test_connect(retryTime=15):
+ for i in range(retryTime):
+ try:
+ r = requests.get(config.BASE_URL)
+ return True
+ except:
+ if i == retryTime - 1:
+ return traceback.format_exc()
+
+def contact_deep_copy(core, contact):
+ with core.storageClass.updateLock:
+ return copy.deepcopy(contact)
+
+def get_image_postfix(data):
+ data = data[:20]
+ if b'GIF' in data:
+ return 'gif'
+ elif b'PNG' in data:
+ return 'png'
+ elif b'JFIF' in data:
+ return 'jpg'
+ return ''
+
+def update_info_dict(oldInfoDict, newInfoDict):
+ ''' only normal values will be updated here
+ because newInfoDict is normal dict, so it's not necessary to consider templates
+ '''
+ for k, v in newInfoDict.items():
+ if any((isinstance(v, t) for t in (tuple, list, dict))):
+ pass # these values will be updated somewhere else
+ elif oldInfoDict.get(k) is None or v not in (None, '', '0', 0):
+ oldInfoDict[k] = v