├── docs
├── bootstrap
│ ├── __init__.py
│ ├── main.html
│ ├── img
│ │ └── favicon.ico
│ ├── fonts
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.ttf
│ │ └── fontawesome-webfont.woff
│ ├── content.html
│ ├── js
│ │ └── base.js
│ ├── toc.html
│ ├── css
│ │ ├── highlight.css
│ │ └── base.css
│ ├── base.html
│ └── nav.html
├── requirements.txt
├── intro
│ ├── deploy.md
│ ├── start.md
│ ├── reply.md
│ ├── handler.md
│ ├── login.md
│ ├── contact.md
│ └── messages.md
├── FAQ.md
├── tutorial
│ ├── tutorial0.md
│ ├── tutorial2.md
│ └── tutorial1.md
├── index.md
└── api.md
├── .gitignore
├── itchat
├── config.py
├── components
│ ├── __init__.py
│ ├── hotreload.py
│ ├── register.py
│ └── login.py
├── content.py
├── storage
│ ├── messagequeue.py
│ ├── __init__.py
│ └── templates.py
├── log.py
├── returnvalues.py
├── __init__.py
├── utils.py
└── core.py
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE.md
├── mkdocs.yml
├── LICENSE
├── setup.py
├── README.md
├── README.rst
└── README_EN.md
/docs/bootstrap/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | mkdocs==0.16.0
2 |
--------------------------------------------------------------------------------
/docs/bootstrap/main.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
--------------------------------------------------------------------------------
/docs/bootstrap/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binhetech/ItChat/master/docs/bootstrap/img/favicon.ico
--------------------------------------------------------------------------------
/docs/bootstrap/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binhetech/ItChat/master/docs/bootstrap/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/docs/bootstrap/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binhetech/ItChat/master/docs/bootstrap/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/docs/bootstrap/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binhetech/ItChat/master/docs/bootstrap/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | tests/*
4 | itchat.egg-info/*
5 | *.pyc
6 | *.pkl
7 | *.swp
8 | test.py
9 | itchat.pkl
10 | QR.jpg
11 | .DS_Store
12 |
--------------------------------------------------------------------------------
/docs/intro/deploy.md:
--------------------------------------------------------------------------------
1 | # 部署
2 |
3 | ## Windows系统
4 |
5 | 直接运行即可。
6 |
7 | ## Linux系统
8 |
9 | 若需要后台命令行运行可以:
10 | ```bash
11 | nohup python run.py &
12 | ```
13 |
14 | 之后手动打开二维码扫描或者使用[一些方式](https://github.com/littlecodersh/EasierLife/tree/master/Plugins/QRCode)让二维码在命令行显示。
15 |
--------------------------------------------------------------------------------
/docs/bootstrap/content.html:
--------------------------------------------------------------------------------
1 | {% if page and page.meta and page.meta.source %}
2 |
3 | {% for filename in page.meta.source %}
4 | {{ filename }}
5 | {% endfor %}
6 |
7 | {% endif %}
8 |
9 | {% if page and page.content %}{{ page.content }}{% endif %}
10 |
--------------------------------------------------------------------------------
/itchat/config.py:
--------------------------------------------------------------------------------
1 | import os, platform
2 |
3 | VERSION = '1.3.10'
4 | BASE_URL = 'https://login.weixin.qq.com'
5 | OS = platform.system() # Windows, Linux, Darwin
6 | DIR = os.getcwd()
7 | DEFAULT_QR = 'QR.png'
8 | TIMEOUT = (10, 60)
9 |
10 | USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36'
11 |
--------------------------------------------------------------------------------
/itchat/components/__init__.py:
--------------------------------------------------------------------------------
1 | from .contact import load_contact
2 | from .hotreload import load_hotreload
3 | from .login import load_login
4 | from .messages import load_messages
5 | from .register import load_register
6 |
7 | def load_components(core):
8 | load_contact(core)
9 | load_hotreload(core)
10 | load_login(core)
11 | load_messages(core)
12 | load_register(core)
13 |
--------------------------------------------------------------------------------
/docs/bootstrap/js/base.js:
--------------------------------------------------------------------------------
1 |
2 | /* Highlight */
3 | $( document ).ready(function() {
4 | hljs.initHighlightingOnLoad();
5 | $('table').addClass('table table-striped table-hover');
6 | });
7 |
8 |
9 | $('body').scrollspy({
10 | target: '.bs-sidebar',
11 | });
12 |
13 |
14 | /* Prevent disabled links from causing a page reload */
15 | $("li.disabled a").click(function() {
16 | event.preventDefault();
17 | });
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/itchat/content.py:
--------------------------------------------------------------------------------
1 | TEXT = 'Text'
2 | MAP = 'Map'
3 | CARD = 'Card'
4 | NOTE = 'Note'
5 | SHARING = 'Sharing'
6 | PICTURE = 'Picture'
7 | RECORDING = VOICE = 'Recording'
8 | ATTACHMENT = 'Attachment'
9 | VIDEO = 'Video'
10 | FRIENDS = 'Friends'
11 | SYSTEM = 'System'
12 |
13 | INCOME_MSG = [TEXT, MAP, CARD, NOTE, SHARING, PICTURE,
14 | RECORDING, VOICE, ATTACHMENT, VIDEO, FRIENDS, SYSTEM]
15 |
--------------------------------------------------------------------------------
/docs/bootstrap/toc.html:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **(阅读后请删除所有内容)**
2 |
3 | 感谢您的pull request! `itchat`没有你们的帮助很难发展到今天,感谢您的贡献.
4 |
5 | 以下简单检查项目望您复查:
6 |
7 | - [ ] 如果您预计提出两个或更多不相关补丁,请为每个使用不同的pull requests,而不是单一;
8 | - [ ] 所有的pull requests应基于最新的`master`分支;
9 | - [ ] 您预计提出pull requests的分支应有有意义名称,例如`add-this-shining-feature`而不是`develop`;
10 | - [ ] 所有的提交信息与代码中注释应使用可理解的英语.
11 |
12 | 作为贡献者,您需要知悉
13 |
14 | - [ ] 您同意在MIT协议下贡献代码,以便任何人自由使用或分发;当然,你仍旧保留代码的著作权
15 | - [ ] 你不得贡献非自己编写的代码,除非其属于公有领域或使用MIT协议.
16 |
17 | 不是所有的pull requests都会被合并,然而我认为合并/不合并的补丁一样重要。
18 | 如果您认为补丁重要,其他人也有可能这么认为,那么他们可以从你的fork中提取工作并获益。
19 | 无论如何,感谢您费心对本项目贡献.
20 |
21 | 祝好,
22 |
23 | LittleCoder, 170308
24 |
25 | **(请将本内容完整替换为PULL REQUEST的详细内容)**
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 在提交前,请确保您已经检查了以下内容!
2 |
3 | - [ ] 您可以在浏览器中登陆微信账号,但不能使用`itchat`登陆
4 | - [ ] 我已经阅读并按[文档][document] 中的指引进行了操作
5 | - [ ] 您的问题没有在[issues][issues]报告,否则请在原有issue下报告
6 | - [ ] 本问题确实关于`itchat`, 而不是其他项目.
7 | - [ ] 如果你的问题关于稳定性,建议尝试对网络稳定性要求极低的[itchatmp][itchatmp]项目
8 |
9 | 请使用`itchat.run(debug=True)`运行,并将输出粘贴在下面:
10 |
11 | ```
12 | [在这里粘贴完整日志]
13 | ```
14 |
15 | 您的itchat版本为:`[在这里填写版本号]`。(可通过`python -c "import itchat;print(itchat.__version__)"`获取)
16 |
17 | 其他的内容或者问题更详细的描述都可以添加在下面:
18 |
19 | > [您的内容]
20 |
21 | [document]: http://itchat.readthedocs.io/zh/latest/
22 | [issues]: https://github.com/littlecodersh/itchat/issues
23 | [itchatmp]: https://github.com/littlecodersh/itchatmp
24 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: 'itchat'
2 | pages:
3 | - '介绍':
4 | - '项目简介': 'index.md'
5 | - '快速入门': 'intro/start.md'
6 | - '登陆配置': 'intro/login.md'
7 | - '回复方法': 'intro/reply.md'
8 | - '注册方法': 'intro/handler.md'
9 | - '消息内容': 'intro/messages.md'
10 | - '各类账号': 'intro/contact.md'
11 | - '部署程序': 'intro/deploy.md'
12 | - 'API列表': 'api.md'
13 | - 'FAQ': 'FAQ.md'
14 | - '教程文章':
15 | - '基础新手入门': 'tutorial/tutorial0.md'
16 | - '实践项目分享': 'tutorial/tutorial2.md'
17 | - '抓包及原理入门': 'tutorial/tutorial1.md'
18 | extra_css: ['docs/bootstrap/css']
19 | extra_javascript: ['docs/bootstrap/js']
20 | theme_dir: 'docs/bootstrap'
21 | repo_url: 'https://github.com/littlecodersh/itchat'
22 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 | ## 稳定性
2 |
3 | Q: itchat稳定性如何?
4 |
5 | A: 测试用机器人能稳定在线多个月。如果你在测试过程中发现无法稳定登陆,请检查**登陆手机**及主机是否稳定连接网络。如果你需要最稳定的消息环境,建议使用[itchatmp][itchatmp]项目,两者使用方法类似。
6 |
7 | ## 中文文件名文件上传
8 |
9 | Q: 为什么中文的文件没有办法上传?
10 |
11 | A: 这是由于`requests`的编码问题导致的。若需要支持中文文件传输,将[fields.py][fields.py-2](py3版本见[这里][fields.py-3])文件放入requests包的packages/urllib3下即可
12 |
13 | ## 命令行显示二维码
14 |
15 | Q: 为什么我在设定了`itchat.auto_login()`的`enableCmdQR`为`True`后还是没有办法在命令行显示二维码?
16 |
17 | A: 这是由于没有安装可选的包`pillow`,可以使用右边的命令安装:`pip install pillow`
18 |
19 | ## 如何通过itchat实现控制器
20 |
21 | Q: 如何通过这个包将自己的微信号变为控制器?
22 |
23 | A: 有两种方式:发送、接受自己UserName的消息;发送接收文件传输助手(filehelper)的消息
24 |
25 | ## 无法给自己发送消息
26 |
27 | Q: 为什么我发送信息的时候部分信息没有成功发出来?
28 |
29 | A: 有些账号是天生无法给自己的账号发送信息的,建议使用`filehelper`代替。
30 |
31 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
32 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
33 | [itchatmp]: https://github.com/littlecodersh/itchatmp
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | **The MIT License (MIT)**
2 |
3 | Copyright (c) 2017 LittleCoder ([littlecodersh@Github](https://github.com/littlecodersh))
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
11 |
--------------------------------------------------------------------------------
/itchat/storage/messagequeue.py:
--------------------------------------------------------------------------------
1 | import logging
2 | try:
3 | import Queue as queue
4 | except ImportError:
5 | import queue
6 |
7 | from .templates import AttributeDict
8 |
9 | logger = logging.getLogger('itchat')
10 |
11 | class Queue(queue.Queue):
12 | def put(self, message):
13 | queue.Queue.put(self, Message(message))
14 |
15 | class Message(AttributeDict):
16 | def download(self, fileName):
17 | if hasattr(self.text, '__call__'):
18 | return self.text(fileName)
19 | else:
20 | return b''
21 | def __getitem__(self, value):
22 | if value in ('isAdmin', 'isAt'):
23 | v = value[0].upper() + value[1:] # ''[1:] == ''
24 | logger.debug('%s is expired in 1.3.0, use %s instead.' % (value, v))
25 | value = v
26 | return super(Message, self).__getitem__(value)
27 | def __str__(self):
28 | return '{%s}' % ', '.join(
29 | ['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
30 | def __repr__(self):
31 | return '<%s: %s>' % (self.__class__.__name__.split('.')[-1],
32 | self.__str__())
33 |
--------------------------------------------------------------------------------
/docs/intro/start.md:
--------------------------------------------------------------------------------
1 | # 入门
2 |
3 | ## 最简单的回复
4 |
5 | 通过如下代码,可以完成回复所有文本信息(包括群聊,公众号)。
6 |
7 | ```python
8 | import itchat
9 | from itchat.content import TEXT
10 |
11 | @itchat.msg_register(TEXT, isFriendChat=True, isGroupChat=True, isMpChat=True)
12 | def simple_reply(msg):
13 | return 'I received: %s' % msg.text
14 |
15 | itchat.auto_login(True)
16 | itchat.run()
17 | ```
18 |
19 | ## 常用消息的配置
20 |
21 | itchat支持所有的消息类型与群聊,下面的示例中演示了对于这些消息类型简单的配置。
22 |
23 | ```python
24 | import itchat, time
25 | from itchat.content import *
26 |
27 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
28 | def text_reply(msg):
29 | msg.user.send('%s: %s' % (msg.type, msg.text))
30 |
31 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
32 | def download_files(msg):
33 | msg.download(msg.fileName)
34 | typeSymbol = {
35 | PICTURE: 'img',
36 | VIDEO: 'vid', }.get(msg.type, 'fil')
37 | return '@%s@%s' % (typeSymbol, msg.fileName)
38 |
39 | @itchat.msg_register(FRIENDS)
40 | def add_friend(msg):
41 | msg.user.verify()
42 | msg.user.send('Nice to meet you!')
43 |
44 | @itchat.msg_register(TEXT, isGroupChat=True)
45 | def text_reply(msg):
46 | if msg.isAt:
47 | msg.user.send(u'@%s\u2005I received: %s' % (
48 | msg.actualNickName, msg.text))
49 |
50 | itchat.auto_login(True)
51 | itchat.run(True)
52 | ```
53 |
54 | 当然这里不需要深究为什么这些东西可以这么写,我在这里放出了示例程序只是为了给你一个该sdk相关代码大概样子的概念。
55 |
56 | 有了大概的模式的了解之后我们就可以进入下一部分的介绍。
57 |
--------------------------------------------------------------------------------
/itchat/log.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | class LogSystem(object):
4 | handlerList = []
5 | showOnCmd = True
6 | loggingLevel = logging.INFO
7 | loggingFile = None
8 | def __init__(self):
9 | self.logger = logging.getLogger('itchat')
10 | self.logger.addHandler(logging.NullHandler())
11 | self.logger.setLevel(self.loggingLevel)
12 | self.cmdHandler = logging.StreamHandler()
13 | self.fileHandler = None
14 | self.logger.addHandler(self.cmdHandler)
15 | def set_logging(self, showOnCmd=True, loggingFile=None,
16 | loggingLevel=logging.INFO):
17 | if showOnCmd != self.showOnCmd:
18 | if showOnCmd:
19 | self.logger.addHandler(self.cmdHandler)
20 | else:
21 | self.logger.removeHandler(self.cmdHandler)
22 | self.showOnCmd = showOnCmd
23 | if loggingFile != self.loggingFile:
24 | if self.loggingFile is not None: # clear old fileHandler
25 | self.logger.removeHandler(self.fileHandler)
26 | self.fileHandler.close()
27 | if loggingFile is not None: # add new fileHandler
28 | self.fileHandler = logging.FileHandler(loggingFile)
29 | self.logger.addHandler(self.fileHandler)
30 | self.loggingFile = loggingFile
31 | if loggingLevel != self.loggingLevel:
32 | self.logger.setLevel(loggingLevel)
33 | self.loggingLevel = loggingLevel
34 |
35 | ls = LogSystem()
36 | set_logging = ls.set_logging
37 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """ A wechat personal account api project
2 | See:
3 | https://github.com/littlecodersh/ItChat
4 | """
5 |
6 | from setuptools import setup, find_packages
7 | from codecs import open
8 | from os import path
9 | import itchat
10 |
11 | here = path.abspath(path.dirname(__file__))
12 |
13 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
14 | long_description = f.read()
15 |
16 | setup(
17 | name='itchat',
18 |
19 | version=itchat.__version__,
20 |
21 | description='A complete wechat personal account api',
22 | long_description=long_description,
23 |
24 | url='https://github.com/littlecodersh/ItChat',
25 |
26 | author='LittleCoder',
27 | author_email='i7meavnktqegm1b@qq.com',
28 |
29 | license='MIT',
30 |
31 | classifiers=[
32 | 'Development Status :: 3 - Alpha',
33 |
34 | 'Intended Audience :: Developers',
35 | 'Topic :: Software Development :: Libraries :: Python Modules',
36 |
37 | 'License :: OSI Approved :: MIT License',
38 |
39 | 'Programming Language :: Python :: 2',
40 | 'Programming Language :: Python :: 2.6',
41 | 'Programming Language :: Python :: 2.7',
42 | ],
43 |
44 | keywords='wechat itchat api robot weixin personal extend',
45 |
46 | # You can just specify the packages manually here if your project is
47 | # simple. Or you can use find_packages().
48 | packages=find_packages(),
49 |
50 | install_requires=['requests', 'pyqrcode', 'pypng'],
51 |
52 | # List additional groups of dependencies here
53 | extras_require={},
54 | )
55 |
--------------------------------------------------------------------------------
/docs/bootstrap/css/highlight.css:
--------------------------------------------------------------------------------
1 | /*
2 | This is the GitHub theme for highlight.js
3 |
4 | github.com style (c) Vasily Polovnyov
5 |
6 | */
7 |
8 | .hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 0.5em;
12 | color: #333;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs-comment,
17 | .diff .hljs-header,
18 | .hljs-javadoc {
19 | color: #998;
20 | font-style: italic;
21 | }
22 |
23 | .hljs-keyword,
24 | .css .rule .hljs-keyword,
25 | .hljs-winutils,
26 | .nginx .hljs-title,
27 | .hljs-subst,
28 | .hljs-request,
29 | .hljs-status {
30 | color: #333;
31 | font-weight: bold;
32 | }
33 |
34 | .hljs-number,
35 | .hljs-hexcolor,
36 | .ruby .hljs-constant {
37 | color: #008080;
38 | }
39 |
40 | .hljs-string,
41 | .hljs-tag .hljs-value,
42 | .hljs-phpdoc,
43 | .hljs-dartdoc,
44 | .tex .hljs-formula {
45 | color: #d14;
46 | }
47 |
48 | .hljs-title,
49 | .hljs-id,
50 | .scss .hljs-preprocessor {
51 | color: #900;
52 | font-weight: bold;
53 | }
54 |
55 | .hljs-list .hljs-keyword,
56 | .hljs-subst {
57 | font-weight: normal;
58 | }
59 |
60 | .hljs-class .hljs-title,
61 | .hljs-type,
62 | .vhdl .hljs-literal,
63 | .tex .hljs-command {
64 | color: #458;
65 | font-weight: bold;
66 | }
67 |
68 | .hljs-tag,
69 | .hljs-tag .hljs-title,
70 | .hljs-rule .hljs-property,
71 | .django .hljs-tag .hljs-keyword {
72 | color: #000080;
73 | font-weight: normal;
74 | }
75 |
76 | .hljs-attribute,
77 | .hljs-variable,
78 | .lisp .hljs-body,
79 | .hljs-name {
80 | color: #008080;
81 | }
82 |
83 | .hljs-regexp {
84 | color: #009926;
85 | }
86 |
87 | .hljs-symbol,
88 | .ruby .hljs-symbol .hljs-string,
89 | .lisp .hljs-keyword,
90 | .clojure .hljs-keyword,
91 | .scheme .hljs-keyword,
92 | .tex .hljs-special,
93 | .hljs-prompt {
94 | color: #990073;
95 | }
96 |
97 | .hljs-built_in {
98 | color: #0086b3;
99 | }
100 |
101 | .hljs-preprocessor,
102 | .hljs-pragma,
103 | .hljs-pi,
104 | .hljs-doctype,
105 | .hljs-shebang,
106 | .hljs-cdata {
107 | color: #999;
108 | font-weight: bold;
109 | }
110 |
111 | .hljs-deletion {
112 | background: #fdd;
113 | }
114 |
115 | .hljs-addition {
116 | background: #dfd;
117 | }
118 |
119 | .diff .hljs-change {
120 | background: #0086b3;
121 | }
122 |
123 | .hljs-chunk {
124 | color: #aaa;
125 | }
126 |
--------------------------------------------------------------------------------
/docs/intro/reply.md:
--------------------------------------------------------------------------------
1 | # 回复
2 |
3 | itchat提供五种回复方法,建议直接使用`send`方法。
4 |
5 | ## send方法
6 |
7 | * 方法:
8 | ```python
9 | send(msg='Text Message', toUserName=None)
10 | ```
11 | * 所需值:
12 | * msg:消息内容
13 | * '@fil@文件地址'将会被识别为传送文件,'@img@图片地址'将会被识别为传送图片,'@vid@视频地址'将会被识别为小视频
14 | * toUserName:发送对象,如果留空将会发送给自己
15 | * 返回值:发送成功->True, 失败->False
16 | * 程序示例:使用的素材可以在[这里][attachment]下载
17 |
18 | ```python
19 | #coding=utf8
20 | import itchat
21 |
22 | itchat.auto_login()
23 | itchat.send('Hello world!')
24 | # 请确保该程序目录下存在:gz.gif以及xlsx.xlsx
25 | itchat.send('@img@%s' % 'gz.gif')
26 | itchat.send('@fil@%s' % 'xlsx.xlsx')
27 | itchat.send('@vid@%s' % 'demo.mp4')
28 | ```
29 |
30 | ## send_msg方法
31 |
32 | * 方法:
33 | ```python
34 | send_msg(msg='Text Message', toUserName=None)
35 | ```
36 | * 所需值:
37 | * msg:消息内容
38 | * toUserName:发送对象,如果留空将会发送给自己
39 | * 返回值:发送成功->True, 失败->False
40 | * 程序示例:
41 |
42 | ```python
43 | import itchat
44 |
45 | itchat.auto_login()
46 | itchat.send_msg('Hello world')
47 | ```
48 |
49 | ## send_file方法
50 |
51 | * 方法:
52 | ```python
53 | send_file(fileDir, toUserName=None)
54 | ```
55 | * 所需值:
56 | * fileDir:文件路径(不存在该文件时将打印无此文件的提醒)
57 | * toUserName:发送对象,如果留空将会发送给自己
58 | * 返回值:发送成功->True, 失败->False
59 | * 程序示例:使用的素材可以在[这里][attachment](提取码:eaee)下载
60 |
61 | ```python
62 | #coding=utf8
63 | import itchat
64 |
65 | itchat.auto_login()
66 | # 请确保该程序目录下存在:xlsx.xlsx
67 | itchat.send_file('xlsx.xlsx')
68 | ```
69 |
70 | ## send_img方法
71 |
72 | * 方法:
73 | ```python
74 | send_img(fileDir, toUserName=None)
75 | ```
76 | * 所需值:
77 | * fileDir:文件路径(不存在该文件时将打印无此文件的提醒)
78 | * toUserName:发送对象,如果留空将会发送给自己
79 | * 返回值:发送成功->True, 失败->False
80 | * 程序示例:使用的素材可以在[这里][attachment](提取码:eaee)下载
81 |
82 | ```python
83 | #coding=utf8
84 | import itchat
85 |
86 | itchat.auto_login()
87 | # 请确保该程序目录下存在:gz.gif
88 | itchat.send_img('gz.gif')
89 | ```
90 |
91 | ## send_video方法
92 |
93 | * 方法:
94 | ```python
95 | send_video(fileDir, toUserName=None)
96 | ```
97 | * 所需值:
98 | * fileDir:文件路径(不存在该文件时将打印无此文件的提醒)
99 | * toUserName:发送对象,如果留空将会发送给自己
100 | * 返回值:发送成功->True, 失败->False
101 | * 需要保证发送的视频为一个实质的mp4文件
102 |
103 | ```python
104 | #coding=utf8
105 | import itchat
106 |
107 | itchat.auto_login()
108 | # 请确保该程序目录下存在:demo.mp4
109 | itchat.send_file('demo.mp4')
110 | ```
111 |
112 | [attachment]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/%E4%B8%8A%E4%BC%A0%E7%B4%A0%E6%9D%90.zip
113 |
--------------------------------------------------------------------------------
/itchat/returnvalues.py:
--------------------------------------------------------------------------------
1 | #coding=utf8
2 | TRANSLATE = 'Chinese'
3 |
4 | class ReturnValue(dict):
5 | ''' turn return value of itchat into a boolean value
6 | for requests:
7 | ..code::python
8 |
9 | import requests
10 | r = requests.get('http://httpbin.org/get')
11 | print(ReturnValue(rawResponse=r)
12 |
13 | for normal dict:
14 | ..code::python
15 |
16 | returnDict = {
17 | 'BaseResponse': {
18 | 'Ret': 0,
19 | 'ErrMsg': 'My error msg', }, }
20 | print(ReturnValue(returnDict))
21 | '''
22 | def __init__(self, returnValueDict={}, rawResponse=None):
23 | if rawResponse:
24 | try:
25 | returnValueDict = rawResponse.json()
26 | except ValueError:
27 | returnValueDict = {
28 | 'BaseResponse': {
29 | 'Ret': -1004,
30 | 'ErrMsg': 'Unexpected return value', },
31 | 'Data': rawResponse.content, }
32 | for k, v in returnValueDict.items():
33 | self[k] = v
34 | if not 'BaseResponse' in self:
35 | self['BaseResponse'] = {
36 | 'ErrMsg': 'no BaseResponse in raw response',
37 | 'Ret': -1000, }
38 | if TRANSLATE:
39 | self['BaseResponse']['RawMsg'] = self['BaseResponse'].get('ErrMsg', '')
40 | self['BaseResponse']['ErrMsg'] = \
41 | TRANSLATION[TRANSLATE].get(
42 | self['BaseResponse'].get('Ret', '')) \
43 | or self['BaseResponse'].get('ErrMsg', u'No ErrMsg')
44 | self['BaseResponse']['RawMsg'] = \
45 | self['BaseResponse']['RawMsg'] or self['BaseResponse']['ErrMsg']
46 | def __nonzero__(self):
47 | return self['BaseResponse'].get('Ret') == 0
48 | def __bool__(self):
49 | return self.__nonzero__()
50 | def __str__(self):
51 | return '{%s}' % ', '.join(
52 | ['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
53 | def __repr__(self):
54 | return '' % self.__str__()
55 |
56 | TRANSLATION = {
57 | 'Chinese': {
58 | -1000: u'返回值不带BaseResponse',
59 | -1001: u'无法找到对应的成员',
60 | -1002: u'文件位置错误',
61 | -1003: u'服务器拒绝连接',
62 | -1004: u'服务器返回异常值',
63 | -1005: u'参数错误',
64 | -1006: u'无效操作',
65 | 0: u'请求成功',
66 | },
67 | }
68 |
--------------------------------------------------------------------------------
/docs/bootstrap/css/base.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 70px;
3 | }
4 |
5 | h1[id]:before, h2[id]:before, h3[id]:before, h4[id]:before, h5[id]:before, h6[id]:before {
6 | content: "";
7 | display: block;
8 | margin-top: -75px;
9 | height: 75px;
10 | }
11 |
12 | ul.nav li.main {
13 | font-weight: bold;
14 | }
15 |
16 | div.col-md-3 {
17 | padding-left: 0;
18 | }
19 |
20 | div.col-md-9 {
21 | padding-bottom: 100px;
22 | }
23 |
24 | div.source-links {
25 | float: right;
26 | }
27 |
28 | div.col-md-9 img {
29 | max-width: 100%;
30 | }
31 |
32 | /*
33 | * Side navigation
34 | *
35 | * Scrollspy and affixed enhanced navigation to highlight sections and secondary
36 | * sections of docs content.
37 | */
38 |
39 | /* By default it's not affixed in mobile views, so undo that */
40 | .bs-sidebar.affix {
41 | position: static;
42 | }
43 |
44 | .bs-sidebar.well {
45 | padding: 0;
46 | }
47 |
48 | /* First level of nav */
49 | .bs-sidenav {
50 | margin-top: 30px;
51 | margin-bottom: 30px;
52 | padding-top: 10px;
53 | padding-bottom: 10px;
54 | border-radius: 5px;
55 | }
56 |
57 | /* All levels of nav */
58 | .bs-sidebar .nav > li > a {
59 | display: block;
60 | padding: 5px 20px;
61 | z-index: 1;
62 | }
63 | .bs-sidebar .nav > li > a:hover,
64 | .bs-sidebar .nav > li > a:focus {
65 | text-decoration: none;
66 | border-right: 1px solid;
67 | }
68 | .bs-sidebar .nav > .active > a,
69 | .bs-sidebar .nav > .active:hover > a,
70 | .bs-sidebar .nav > .active:focus > a {
71 | font-weight: bold;
72 | background-color: transparent;
73 | border-right: 1px solid;
74 | }
75 |
76 | /* Nav: second level (shown on .active) */
77 | .bs-sidebar .nav .nav {
78 | display: none; /* Hide by default, but at >768px, show it */
79 | margin-bottom: 8px;
80 | }
81 | .bs-sidebar .nav .nav > li > a {
82 | padding-top: 3px;
83 | padding-bottom: 3px;
84 | padding-left: 30px;
85 | font-size: 90%;
86 | }
87 |
88 | /* Show and affix the side nav when space allows it */
89 | @media (min-width: 992px) {
90 | .bs-sidebar .nav > .active > ul {
91 | display: block;
92 | }
93 | /* Widen the fixed sidebar */
94 | .bs-sidebar.affix,
95 | .bs-sidebar.affix-bottom {
96 | width: 213px;
97 | }
98 | .bs-sidebar.affix {
99 | position: fixed; /* Undo the static from mobile first approach */
100 | top: 80px;
101 | }
102 | .bs-sidebar.affix-bottom {
103 | position: absolute; /* Undo the static from mobile first approach */
104 | }
105 | .bs-sidebar.affix-bottom .bs-sidenav,
106 | .bs-sidebar.affix .bs-sidenav {
107 | margin-top: 0;
108 | margin-bottom: 0;
109 | }
110 | }
111 | @media (min-width: 1200px) {
112 | /* Widen the fixed sidebar again */
113 | .bs-sidebar.affix-bottom,
114 | .bs-sidebar.affix {
115 | width: 263px;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/itchat/__init__.py:
--------------------------------------------------------------------------------
1 | from . import content
2 | from .core import Core
3 | from .config import VERSION
4 | from .log import set_logging
5 |
6 | __version__ = VERSION
7 |
8 | instanceList = []
9 |
10 | def new_instance():
11 | newInstance = Core()
12 | instanceList.append(newInstance)
13 | return newInstance
14 |
15 | originInstance = new_instance()
16 |
17 | # I really want to use sys.modules[__name__] = originInstance
18 | # but it makes auto-fill a real mess, so forgive me for my following **
19 | # actually it toke me less than 30 seconds, god bless Uganda
20 |
21 | # components.login
22 | login = originInstance.login
23 | get_QRuuid = originInstance.get_QRuuid
24 | get_QR = originInstance.get_QR
25 | check_login = originInstance.check_login
26 | web_init = originInstance.web_init
27 | show_mobile_login = originInstance.show_mobile_login
28 | start_receiving = originInstance.start_receiving
29 | get_msg = originInstance.get_msg
30 | logout = originInstance.logout
31 | # components.contact
32 | update_chatroom = originInstance.update_chatroom
33 | update_friend = originInstance.update_friend
34 | get_contact = originInstance.get_contact
35 | get_friends = originInstance.get_friends
36 | get_chatrooms = originInstance.get_chatrooms
37 | get_mps = originInstance.get_mps
38 | set_alias = originInstance.set_alias
39 | set_pinned = originInstance.set_pinned
40 | add_friend = originInstance.add_friend
41 | get_head_img = originInstance.get_head_img
42 | create_chatroom = originInstance.create_chatroom
43 | set_chatroom_name = originInstance.set_chatroom_name
44 | delete_member_from_chatroom = originInstance.delete_member_from_chatroom
45 | add_member_into_chatroom = originInstance.add_member_into_chatroom
46 | # components.messages
47 | send_raw_msg = originInstance.send_raw_msg
48 | send_msg = originInstance.send_msg
49 | upload_file = originInstance.upload_file
50 | send_file = originInstance.send_file
51 | send_image = originInstance.send_image
52 | send_video = originInstance.send_video
53 | send = originInstance.send
54 | revoke = originInstance.revoke
55 | # components.hotreload
56 | dump_login_status = originInstance.dump_login_status
57 | load_login_status = originInstance.load_login_status
58 | # components.register
59 | auto_login = originInstance.auto_login
60 | configured_reply = originInstance.configured_reply
61 | msg_register = originInstance.msg_register
62 | run = originInstance.run
63 | # other functions
64 | search_friends = originInstance.search_friends
65 | search_chatrooms = originInstance.search_chatrooms
66 | search_mps = originInstance.search_mps
67 | set_logging = set_logging
68 |
--------------------------------------------------------------------------------
/docs/bootstrap/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {%- block site_meta %}
5 |
6 |
7 |
8 | {% if config.site_description %}{% endif %}
9 | {% if config.site_author %}{% endif %}
10 | {% if page.canonical_url %}{% endif %}
11 |
12 | {%- endblock %}
13 |
14 | {%- block htmltitle %}
15 | {% if page and page.title %}{{ page.title }} - {% endif %}{{ config.site_name }}
16 | {%- endblock %}
17 |
18 | {%- block styles %}
19 |
20 |
21 |
22 |
23 | {%- for path in extra_css %}
24 |
25 | {%- endfor %}
26 | {%- endblock %}
27 |
28 | {%- block libs %}
29 |
30 |
34 |
35 |
36 |
37 |
38 | {%- endblock %}
39 |
40 | {%- block analytics %}
41 | {% if config.google_analytics %}
42 |
51 | {% endif %}
52 | {%- endblock %}
53 |
54 | {%- block extrahead %} {% endblock %}
55 |
56 |
57 |
58 |
59 | {% include "nav.html" %}
60 |
61 |
62 | {%- block content %}
63 |
{% include "toc.html" %}
64 |
{% include "content.html" %}
65 | {%- endblock %}
66 |
67 |
68 |
76 |
77 | {%- block scripts %}
78 |
79 | {%- for path in extra_javascript %}
80 |
81 | {%- endfor %}
82 | {%- endblock %}
83 |
84 |
85 |
--------------------------------------------------------------------------------
/docs/bootstrap/nav.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 | {%- block site_nav %}
22 |
23 |
43 | {%- endblock %}
44 |
45 |
46 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/docs/intro/handler.md:
--------------------------------------------------------------------------------
1 | # 注册消息方法
2 |
3 | itchat将根据接收到的消息类型寻找对应的已经注册的方法。
4 |
5 | 如果一个消息类型没有对应的注册方法,该消息将会被舍弃。
6 |
7 | 在运行过程当中也可以动态注册方法,注册方式与结果不变。
8 |
9 | ## 注册
10 |
11 | 你可以通过两种方式注册消息方法
12 |
13 | ```python
14 | import itchat
15 | from itchat.content import *
16 |
17 | # 不带具体对象注册,将注册为普通消息的回复方法
18 | @itchat.msg_register(TEXT)
19 | def simple_reply(msg):
20 | return 'I received: %s' % msg['Text']
21 |
22 | # 带对象参数注册,对应消息对象将调用该方法
23 | @itchat.msg_register(TEXT, isFriendChat=True, isGroupChat=True, isMpChat=True)
24 | def text_reply(msg):
25 | msg.user.send('%s: %s' % (msg.type, msg.text))
26 | ```
27 |
28 | ## 消息类型
29 |
30 | 向注册方法传入的msg包含微信返回的字典的所有内容。
31 |
32 | 本api增加`Text`、`Type`(也就是参数)键值,方便操作。
33 |
34 | itchat.content中包含所有的消息类型参数,内容如下表所示:
35 |
36 | 参数 |类型 |Text键值
37 | :----------|:----------|:---------------
38 | TEXT |文本 |文本内容
39 | MAP |地图 |位置文本
40 | CARD |名片 |推荐人字典
41 | NOTE |通知 |通知文本
42 | SHARING |分享 |分享名称
43 | PICTURE |图片/表情 |下载方法
44 | RECORDING |语音 |下载方法
45 | ATTACHMENT |附件 |下载方法
46 | VIDEO |小视频 |下载方法
47 | FRIENDS |好友邀请 |添加好友所需参数
48 | SYSTEM |系统消息 |更新内容的用户或群聊的UserName组成的列表
49 |
50 | 比如你需要存储发送给你的附件:
51 |
52 | ```python
53 | @itchat.msg_register(ATTACHMENT)
54 | def download_files(msg):
55 | msg['Text'](msg['FileName'])
56 | ```
57 |
58 | 值得注意的是,群消息增加了三个键值:
59 |
60 | * isAt: 判断是否@本号
61 | * ActualNickName: 实际NickName
62 | * Content: 实际Content
63 |
64 | 可以通过本程序测试:
65 |
66 | ```python
67 | import itchat
68 | from itchat.content import TEXT
69 |
70 | @itchat.msg_register(TEXT, isGroupChat=True)
71 | def text_reply(msg):
72 | print(msg.isAt)
73 | print(msg.actualNickName)
74 | print(msg.text)
75 |
76 | itchat.auto_login()
77 | itchat.run()
78 | ```
79 |
80 | ## 注册消息的优先级
81 |
82 | 优先级分别为:后注册消息先于先注册消息,带参数消息先于不带参数消息。
83 |
84 | 以下面的两个程序为例:
85 |
86 | ```python
87 | import itchat
88 | from itchat.content import *
89 |
90 | itchat.auto_login()
91 |
92 | @itchat.msg_register(TEXT)
93 | def text_reply(msg):
94 | return 'This is the old register'
95 |
96 | @itchat.msg_register(TEXT)
97 | def text_reply(msg):
98 | return 'This is a new one'
99 |
100 | itchat.run()
101 | ```
102 |
103 | 在私聊发送文本时将会回复`This is a new one`。
104 | ```python
105 | import itchat
106 | from itchat.content import *
107 |
108 | itchat.auto_login()
109 |
110 | @itchat.msg_register
111 | def general_reply(msg):
112 | return 'I received a %s' % msg.type
113 |
114 | @itchat.msg_register(TEXT)
115 | def text_reply(msg):
116 | return 'You said to me one to one: %s' % msg.text
117 |
118 | itchat.run()
119 | ```
120 |
121 | 仅在私聊发送文本时将会回复`You said to me one to one`,其余情况将会回复`I received a ...`。
122 |
123 | ## 动态注册消息
124 |
125 | 动态注册时可以选择将`itchat.run()`放入另一线程或使用`configured_reply()`方法处理消息。
126 |
127 | 两种方法分别是:
128 |
129 | ```python
130 | # 使用另一线程,但注意不要让程序运行终止
131 | import thread
132 |
133 | thread.start_new_thread(itchat.run, ())
134 |
135 | # 使用configured_reply方法
136 | while 1:
137 | itchat.configured_reply()
138 | # some other functions
139 | time.sleep(1)
140 | ```
141 |
142 | 以下给出一个动态注册的例子:
143 |
144 | ```python
145 | #coding=utf8
146 | import thread
147 |
148 | import itchat
149 | from itchat.content import *
150 |
151 | replyToGroupChat = True
152 | functionStatus = False
153 |
154 | def change_function():
155 | if replyToGroupChat != functionStatus:
156 | if replyToGroupChat:
157 | @itchat.msg_register(TEXT, isGroupChat=True)
158 | def group_text_reply(msg):
159 | if u'关闭' in msg['Text']:
160 | replyToGroupChat = False
161 | return u'已关闭'
162 | elif u'开启' in msg['Text']:
163 | return u'已经在运行'
164 | return u'输入"关闭"或者"开启"测试功能'
165 | else:
166 | @itchat.msg_register(TEXT, isGroupChat=True)
167 | def group_text_reply(msg):
168 | if u'开启' in msg['Text']:
169 | replyToGroupChat = True
170 | return u'重新开启成功'
171 | functionStatus = replyToGroupChat
172 |
173 | thread.start_new_thread(itchat.run, ())
174 |
175 | while 1:
176 | change_function()
177 | time.sleep(.1)
178 | ```
179 |
--------------------------------------------------------------------------------
/itchat/components/hotreload.py:
--------------------------------------------------------------------------------
1 | import pickle, os
2 | import logging
3 |
4 | import requests
5 |
6 | from ..config import VERSION
7 | from ..returnvalues import ReturnValue
8 | from ..storage import templates
9 | from .contact import update_local_chatrooms, update_local_friends
10 | from .messages import produce_msg
11 |
12 | logger = logging.getLogger('itchat')
13 |
14 | def load_hotreload(core):
15 | core.dump_login_status = dump_login_status
16 | core.load_login_status = load_login_status
17 |
18 | def dump_login_status(self, fileDir=None):
19 | fileDir = fileDir or self.hotReloadDir
20 | try:
21 | with open(fileDir, 'w') as f:
22 | f.write('itchat - DELETE THIS')
23 | os.remove(fileDir)
24 | except:
25 | raise Exception('Incorrect fileDir')
26 | status = {
27 | 'version' : VERSION,
28 | 'loginInfo' : self.loginInfo,
29 | 'cookies' : self.s.cookies.get_dict(),
30 | 'storage' : self.storageClass.dumps()}
31 | with open(fileDir, 'wb') as f:
32 | pickle.dump(status, f)
33 | logger.debug('Dump login status for hot reload successfully.')
34 |
35 | def load_login_status(self, fileDir,
36 | loginCallback=None, exitCallback=None):
37 | try:
38 | with open(fileDir, 'rb') as f:
39 | j = pickle.load(f)
40 | except Exception as e:
41 | logger.debug('No such file, loading login status failed.')
42 | return ReturnValue({'BaseResponse': {
43 | 'ErrMsg': 'No such file, loading login status failed.',
44 | 'Ret': -1002, }})
45 |
46 | if j.get('version', '') != VERSION:
47 | logger.debug(('you have updated itchat from %s to %s, ' +
48 | 'so cached status is ignored') % (
49 | j.get('version', 'old version'), VERSION))
50 | return ReturnValue({'BaseResponse': {
51 | 'ErrMsg': 'cached status ignored because of version',
52 | 'Ret': -1005, }})
53 | self.loginInfo = j['loginInfo']
54 | self.loginInfo['User'] = templates.User(self.loginInfo['User'])
55 | self.loginInfo['User'].core = self
56 | self.s.cookies = requests.utils.cookiejar_from_dict(j['cookies'])
57 | self.storageClass.loads(j['storage'])
58 | try:
59 | msgList, contactList = self.get_msg()
60 | except:
61 | msgList = contactList = None
62 | if (msgList or contactList) is None:
63 | self.logout()
64 | load_last_login_status(self.s, j['cookies'])
65 | logger.debug('server refused, loading login status failed.')
66 | return ReturnValue({'BaseResponse': {
67 | 'ErrMsg': 'server refused, loading login status failed.',
68 | 'Ret': -1003, }})
69 | else:
70 | if contactList:
71 | for contact in contactList:
72 | if '@@' in contact['UserName']:
73 | update_local_chatrooms(self, [contact])
74 | else:
75 | update_local_friends(self, [contact])
76 | if msgList:
77 | msgList = produce_msg(self, msgList)
78 | for msg in msgList: self.msgList.put(msg)
79 | self.start_receiving(exitCallback)
80 | logger.debug('loading login status succeeded.')
81 | if hasattr(loginCallback, '__call__'):
82 | loginCallback()
83 | return ReturnValue({'BaseResponse': {
84 | 'ErrMsg': 'loading login status succeeded.',
85 | 'Ret': 0, }})
86 |
87 | def load_last_login_status(session, cookiesDict):
88 | try:
89 | session.cookies = requests.utils.cookiejar_from_dict({
90 | 'webwxuvid': cookiesDict['webwxuvid'],
91 | 'webwx_auth_ticket': cookiesDict['webwx_auth_ticket'],
92 | 'login_frequency': '2',
93 | 'last_wxuin': cookiesDict['wxuin'],
94 | 'wxloadtime': cookiesDict['wxloadtime'] + '_expired',
95 | 'wxpluginkey': cookiesDict['wxloadtime'],
96 | 'wxuin': cookiesDict['wxuin'],
97 | 'mm_lang': 'zh_CN',
98 | 'MM_WX_NOTIFY_STATE': '1',
99 | 'MM_WX_SOUND_STATE': '1', })
100 | except:
101 | logger.info('Load status for push login failed, we may have experienced a cookies change.')
102 | logger.info('If you are using the newest version of itchat, you may report a bug.')
103 |
--------------------------------------------------------------------------------
/docs/intro/login.md:
--------------------------------------------------------------------------------
1 | # 登陆
2 |
3 | 在上一章中你看到了基本的注册与登陆,而显然登陆使用的是itchat提供了`auto_login`方法,调用即可完成登录。
4 |
5 | 一般而言,我们都会在完成消息的注册后登陆。
6 |
7 | 当然这里需要特别强调的是三点,分别是短时间关闭重连、命令行二维码与自定义登陆内容。
8 |
9 | * itchat提供了登陆状态暂存,关闭程序后一定时间内不需要扫码即可登录。
10 | * 由于目前微信网页版提供上一次登录的微信号不扫码直接手机确认登陆,所以如果开启登陆状态暂存将会自动使用这一功能。
11 | * 为了方便在无图形界面使用itchat,程序内置了命令行二维码的显示。
12 | * 如果你需要就登录状态就一些修改(例如更改提示语、二维码出现后邮件发送等)。
13 |
14 | ## 短时间关闭程序后重连
15 |
16 | 这样即使程序关闭,一定时间内重新开启也可以不用重新扫码。
17 |
18 | 最简单的用法就是给`auto_login`方法传入值为真的hotReload。
19 |
20 | 该方法会生成一个静态文件`itchat.pkl`,用于存储登陆的状态。
21 |
22 | ```python
23 | import itchat
24 | from itchat.content import TEXT
25 |
26 | @itchat.msg_register(TEXT)
27 | def simple_reply(msg):
28 | print(msg.text)
29 |
30 | itchat.auto_login(hotReload=True)
31 | itchat.run()
32 | ```
33 |
34 | 通过设置statusStorageDir可以将静态文件指定为其他的值。
35 |
36 | 这一内置选项其实就相当于使用了以下两个函数的这一段程序:
37 |
38 | ```python
39 | import itchat
40 | from itchat.content import TEXT
41 |
42 | if itchat.load_login_status():
43 | @itchat.msg_register(TEXT)
44 | def simple_reply(msg):
45 | print(msg['Text'])
46 | itchat.run()
47 | itchat.dump_login_status()
48 | else:
49 | itchat.auto_login()
50 | itchat.dump_login_status()
51 | print('Config stored, so exit.')
52 | ```
53 |
54 | 其中load_login_status与dump_login_status分别对应读取与导出设置。
55 |
56 | 通过设置传入的fileDir的值可以设定导入导出的文件。
57 |
58 | ## 命令行二维码显示
59 |
60 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
61 |
62 | ```python
63 | itchat.auto_login(enableCmdQR=True)
64 | ```
65 |
66 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
67 |
68 | ```python
69 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
70 | itchat.auto_login(enableCmdQR=2)
71 | ```
72 |
73 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
74 |
75 | ```python
76 | itchat.auto_login(enableCmdQR=-1)
77 | ```
78 |
79 | ## 自定义登录过程
80 |
81 | 如果需要控制登录的过程,可以阅读下面的内容。
82 |
83 | 同时itchat也提供了登陆所需的每一步的方法,登陆的过程按顺序为:
84 |
85 | * 获取二维码uuid
86 | * 获取二维码
87 | * 判断是否已经登陆成功
88 | * 获取初始化数据
89 | * 更新微信相关信息(通讯录、手机登陆状态)
90 | * 循环扫描新信息(开启心跳)
91 |
92 | ### 获取二维码uuid
93 |
94 | 获取生成二维码所需的uuid,并返回。
95 |
96 | * 方法名称:`get_QRuuid`
97 | * 所需值:无
98 | * 返回值:成功->uuid,失败->None
99 |
100 | ### 获取二维码
101 |
102 | 根据uuid获取二维码并打开,返回是否成功。
103 |
104 | * 方法名称:`get_QR`
105 | * 所需值:uuid
106 | * 返回值:成功->True,失败->False
107 |
108 | ### 判断是否已经登陆成功
109 |
110 | 判断是否已经登陆成功,返回扫描的状态码。
111 |
112 | * 方法名称:`check_login`
113 | * 所需值:uuid
114 | * 返回值:登陆成功->'200',已扫描二维码->'201',二维码失效->'408',未获取到信息->'0'
115 |
116 | ### 获取初始化数据
117 |
118 | 获取微信用户信息以及心跳所需要的数据。
119 |
120 | * 方法名称:`web_init`
121 | * 所需值:无
122 | * 返回值:存储登录微信用户信息的字典
123 |
124 | ### 获取微信通讯录
125 |
126 | 获取微信的所有好友信息并更新。
127 |
128 | * 方法名称:`get_friends`(曾用名:`get_contact`)
129 | * 所需值:无
130 | * 返回值:存储好友信息的列表
131 |
132 | ### 更新微信手机登陆状态
133 |
134 | 在手机上显示登录状态。
135 |
136 | * 方法名称:`show_mobile_login`
137 | * 所需值:无
138 | * 返回值:无
139 |
140 | ### 循环扫描新信息(开启心跳)
141 |
142 | 循环扫描是否有新的消息,开启心跳包。
143 |
144 | * 方法名称:`start_receiving`
145 | * 所需值:无
146 | * 返回值:无
147 |
148 | ## 示例
149 |
150 | itchat自带的`auto_login`通过如下代码可以实现:
151 |
152 | ```python
153 | import itchat, time, sys
154 |
155 | def output_info(msg):
156 | print('[INFO] %s' % msg)
157 |
158 | def open_QR():
159 | for get_count in range(10):
160 | output_info('Getting uuid')
161 | uuid = itchat.get_QRuuid()
162 | while uuid is None: uuid = itchat.get_QRuuid();time.sleep(1)
163 | output_info('Getting QR Code')
164 | if itchat.get_QR(uuid): break
165 | elif get_count >= 9:
166 | output_info('Failed to get QR Code, please restart the program')
167 | sys.exit()
168 | output_info('Please scan the QR Code')
169 | return uuid
170 |
171 | uuid = open_QR()
172 | waitForConfirm = False
173 | while 1:
174 | status = itchat.check_login(uuid)
175 | if status == '200':
176 | break
177 | elif status == '201':
178 | if waitForConfirm:
179 | output_info('Please press confirm')
180 | waitForConfirm = True
181 | elif status == '408':
182 | output_info('Reloading QR Code')
183 | uuid = open_QR()
184 | waitForConfirm = False
185 | userInfo = itchat.web_init()
186 | itchat.show_mobile_login()
187 | itchat.get_friends(True)
188 | output_info('Login successfully as %s'%userInfo['NickName'])
189 | itchat.start_receiving()
190 |
191 | # Start auto-replying
192 | @itchat.msg_register
193 | def simple_reply(msg):
194 | if msg['Type'] == 'Text':
195 | return 'I received: %s' % msg['Content']
196 | itchat.run()
197 | ```
198 |
--------------------------------------------------------------------------------
/itchat/components/register.py:
--------------------------------------------------------------------------------
1 | import logging, traceback, sys, threading
2 | try:
3 | import Queue
4 | except ImportError:
5 | import queue as Queue
6 |
7 | from ..log import set_logging
8 | from ..utils import test_connect
9 | from ..storage import templates
10 |
11 | logger = logging.getLogger('itchat')
12 |
13 | def load_register(core):
14 | core.auto_login = auto_login
15 | core.configured_reply = configured_reply
16 | core.msg_register = msg_register
17 | core.run = run
18 |
19 | def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
20 | enableCmdQR=False, picDir=None, qrCallback=None,
21 | loginCallback=None, exitCallback=None):
22 | if not test_connect():
23 | logger.info("You can't get access to internet or wechat domain, so exit.")
24 | sys.exit()
25 | self.useHotReload = hotReload
26 | self.hotReloadDir = statusStorageDir
27 | if hotReload:
28 | if self.load_login_status(statusStorageDir,
29 | loginCallback=loginCallback, exitCallback=exitCallback):
30 | return
31 | self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
32 | loginCallback=loginCallback, exitCallback=exitCallback)
33 | self.dump_login_status(statusStorageDir)
34 | else:
35 | self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
36 | loginCallback=loginCallback, exitCallback=exitCallback)
37 |
38 | def configured_reply(self):
39 | ''' determine the type of message and reply if its method is defined
40 | however, I use a strange way to determine whether a msg is from massive platform
41 | I haven't found a better solution here
42 | The main problem I'm worrying about is the mismatching of new friends added on phone
43 | If you have any good idea, pleeeease report an issue. I will be more than grateful.
44 | '''
45 | try:
46 | msg = self.msgList.get(timeout=1)
47 | except Queue.Empty:
48 | pass
49 | else:
50 | if isinstance(msg['User'], templates.User):
51 | replyFn = self.functionDict['FriendChat'].get(msg['Type'])
52 | elif isinstance(msg['User'], templates.MassivePlatform):
53 | replyFn = self.functionDict['MpChat'].get(msg['Type'])
54 | elif isinstance(msg['User'], templates.Chatroom):
55 | replyFn = self.functionDict['GroupChat'].get(msg['Type'])
56 | if replyFn is None:
57 | r = None
58 | else:
59 | try:
60 | r = replyFn(msg)
61 | if r is not None:
62 | self.send(r, msg.get('FromUserName'))
63 | except:
64 | logger.warning(traceback.format_exc())
65 |
66 | def msg_register(self, msgType, isFriendChat=False, isGroupChat=False, isMpChat=False):
67 | ''' a decorator constructor
68 | return a specific decorator based on information given '''
69 | if not (isinstance(msgType, list) or isinstance(msgType, tuple)):
70 | msgType = [msgType]
71 | def _msg_register(fn):
72 | for _msgType in msgType:
73 | if isFriendChat:
74 | self.functionDict['FriendChat'][_msgType] = fn
75 | if isGroupChat:
76 | self.functionDict['GroupChat'][_msgType] = fn
77 | if isMpChat:
78 | self.functionDict['MpChat'][_msgType] = fn
79 | if not any((isFriendChat, isGroupChat, isMpChat)):
80 | self.functionDict['FriendChat'][_msgType] = fn
81 | return fn
82 | return _msg_register
83 |
84 | def run(self, debug=False, blockThread=True):
85 | logger.info('Start auto replying.')
86 | if debug:
87 | set_logging(loggingLevel=logging.DEBUG)
88 | def reply_fn():
89 | try:
90 | while self.alive:
91 | self.configured_reply()
92 | except KeyboardInterrupt:
93 | if self.useHotReload:
94 | self.dump_login_status()
95 | self.alive = False
96 | logger.debug('itchat received an ^C and exit.')
97 | logger.info('Bye~')
98 | if blockThread:
99 | reply_fn()
100 | else:
101 | replyThread = threading.Thread(target=reply_fn)
102 | replyThread.setDaemon(True)
103 | replyThread.start()
104 |
--------------------------------------------------------------------------------
/docs/intro/contact.md:
--------------------------------------------------------------------------------
1 | 在使用个人微信的过程当中主要有三种账号需要获取,分别为:
2 |
3 | * 好友
4 | * 公众号
5 | * 群聊
6 |
7 | itchat为这三种账号都提供了整体获取方法与搜索方法。
8 |
9 | 而群聊多出获取用户列表方法以及创建群聊、增加、删除用户的方法。
10 |
11 | 这里我们分这三种分别介绍如何使用。
12 |
13 | 在本章的最后还介绍了如何通过Uin唯一的确定好友与群聊。
14 |
15 | ## 好友
16 |
17 | 好友的获取方法为`get_friends`,将会返回完整的好友列表。
18 |
19 | * 其中每个好友为一个字典
20 | * 列表的第一项为本人的账号信息
21 | * 传入update键为True将可以更新好友列表并返回
22 |
23 | 好友的搜索方法为`search_friends`,有四种搜索方式:
24 | 1. 仅获取自己的用户信息
25 | 2. 获取特定`UserName`的用户信息
26 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
27 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
28 |
29 | 其中三、四项可以一同使用,下面是示例程序:
30 |
31 | ```python
32 | # 获取自己的用户信息,返回自己的属性字典
33 | itchat.search_friends()
34 | # 获取特定UserName的用户信息
35 | itchat.search_friends(userName='@abcdefg1234567')
36 | # 获取任何一项等于name键值的用户
37 | itchat.search_friends(name='littlecodersh')
38 | # 获取分别对应相应键值的用户
39 | itchat.search_friends(wechatAccount='littlecodersh')
40 | # 三、四项功能可以一同使用
41 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
42 | ```
43 |
44 | 更新用户信息的方法为`update_friend`。
45 |
46 | * 该方法需要传入用户的`UserName`,返回指定用户的最新信息
47 | * 同样也可以传入`UserName`组成的列表,那么相应的也会返回指定用户的最新信息组成的列表
48 |
49 | ```python
50 | memberList = itchat.update_friend('@abcdefg1234567')
51 | ```
52 |
53 | ## 公众号
54 |
55 | 公众号的获取方法为`get_mps`,将会返回完整的公众号列表。
56 |
57 | * 其中每个公众号为一个字典
58 | * 传入update键为True将可以更新公众号列表并返回
59 |
60 | 公众号的搜索方法为`search_mps`,有两种搜索方法:
61 | 1. 获取特定`UserName`的公众号
62 | 2. 获取名字中含有特定字符的公众号
63 |
64 | 如果两项都做了特定,将会仅返回特定`UserName`的公众号,下面是示例程序:
65 |
66 | ```python
67 | # 获取特定UserName的公众号,返回值为一个字典
68 | itchat.search_mps(userName='@abcdefg1234567')
69 | # 获取名字中含有特定字符的公众号,返回值为一个字典的列表
70 | itchat.search_mps(name='LittleCoder')
71 | # 以下方法相当于仅特定了UserName
72 | itchat.search_mps(userName='@abcdefg1234567', name='LittleCoder')
73 | ```
74 |
75 | ## 群聊
76 |
77 | 群聊的获取方法为`get_chatrooms`,将会返回完整的群聊列表。
78 |
79 | * 其中每个群聊为一个字典
80 | * 传入update键为True将可以更新群聊列表并返回通讯录中保存的群聊列表
81 | * 群聊列表为后台自动更新,如果中途意外退出存在极小的概率产生本地群聊消息与后台不同步
82 | * 为了保证群聊信息在热启动中可以被正确的加载,即使不需要持续在线的程序也需要运行`itchat.run()`
83 | * 如果不想要运行上述命令,请在退出程序前调用`itchat.dump_login_status()`,更新热拔插需要的信息
84 |
85 | 群聊的搜索方法为`search_chatrooms`,有两种搜索方法:
86 | 1. 获取特定`UserName`的群聊
87 | 2. 获取名字中含有特定字符的群聊
88 |
89 | 如果两项都做了特定,将会仅返回特定`UserName`的群聊,下面是示例程序:
90 |
91 | ```python
92 | # 获取特定UserName的群聊,返回值为一个字典
93 | itchat.search_chatrooms(userName='@@abcdefg1234567')
94 | # 获取名字中含有特定字符的群聊,返回值为一个字典的列表
95 | itchat.search_chatrooms(name='LittleCoder')
96 | # 以下方法相当于仅特定了UserName
97 | itchat.search_chatrooms(userName='@@abcdefg1234567', name='LittleCoder')
98 | ```
99 |
100 | 群聊用户列表的获取方法为`update_chatroom`。
101 |
102 | * 同样,如果想要更新该群聊的其他信息也可以用该方法
103 | * 群聊在首次获取中不会获取群聊的用户列表,所以需要调用该命令才能获取群聊的成员
104 | * 该方法需要传入群聊的`UserName`,返回特定群聊的详细信息
105 | * 同样也可以传入`UserName`组成的列表,那么相应的也会返回指定用户的最新信息组成的列表
106 |
107 | ```python
108 | memberList = itchat.update_chatroom('@@abcdefg1234567', detailedMember=True)
109 | ```
110 |
111 | 创建群聊、增加、删除群聊用户的方法如下所示:
112 |
113 | * 由于之前通过群聊检测是否被好友拉黑的程序,目前这三个方法都被严格限制了使用频率
114 | * 删除群聊需要本账号为群管理员,否则会失败
115 | * 将用户加入群聊有直接加入与发送邀请,通过`useInvitation`设置
116 | * 超过40人的群聊无法使用直接加入的加入方式,特别注意
117 |
118 | ```python
119 | memberList = itchat.get_friends()[1:]
120 | # 创建群聊,topic键值为群聊名
121 | chatroomUserName = itchat.create_chatroom(memberList, 'test chatroom')
122 | # 删除群聊内的用户
123 | itchat.delete_member_from_chatroom(chatroomUserName, memberList[0])
124 | # 增加用户进入群聊
125 | itchat.add_member_into_chatroom(chatroomUserName, memberList[0], useInvitation=False)
126 | ```
127 |
128 | ## Uins
129 |
130 | Uin 就是微信中用于标识用户的方式,每一个用户、群聊都有唯一且不同的Uin。
131 |
132 | 那么通过Uin,即使退出了重新登录,也可以轻松的确认正在对话的是上一次登陆的哪一个用户。
133 |
134 | 但注意,Uin与其他值不同,微信后台做了一定的限制,必须通过特殊的操作才能获取。
135 |
136 | 最简单来说,首次点开登陆用的手机端的某个好友或者群聊,itchat就能获取到该好友或者群聊的Uin。
137 |
138 | 如果想要通过程序获取,也可以用程序将某个好友或者群聊置顶(取消置顶)。
139 |
140 | 这里提供一个提示群聊更新的程序:
141 |
142 | ```python
143 | import re, sys, json
144 |
145 | import itchat
146 | from itchat.content import *
147 |
148 | itchat.auto_login(True)
149 |
150 | @itchat.msg_register(SYSTEM)
151 | def get_uin(msg):
152 | if msg['SystemInfo'] != 'uins': return
153 | ins = itchat.instanceList[0]
154 | fullContact = ins.memberList + ins.chatroomList + ins.mpList
155 | print('** Uin Updated **')
156 | for username in msg['Text']:
157 | member = itchat.utils.search_dict_list(
158 | fullContact, 'UserName', username)
159 | print(('%s: %s' % (
160 | member.get('NickName', ''), member['Uin']))
161 | .encode(sys.stdin.encoding, 'replace'))
162 |
163 | itchat.run(True)
164 | ```
165 |
166 | 每当Uin更新了,就会打印相应的更新情况。
167 |
168 | 同样的,如果你想要获取Uin更新的情况也通过获取SYSTEM类型消息实现。
169 |
--------------------------------------------------------------------------------
/itchat/storage/__init__.py:
--------------------------------------------------------------------------------
1 | import os, time, copy
2 | from threading import Lock
3 |
4 | from .messagequeue import Queue
5 | from .templates import (
6 | ContactList, AbstractUserDict, User,
7 | MassivePlatform, Chatroom, ChatroomMember)
8 |
9 | def contact_change(fn):
10 | def _contact_change(core, *args, **kwargs):
11 | with core.storageClass.updateLock:
12 | return fn(core, *args, **kwargs)
13 | return _contact_change
14 |
15 | class Storage(object):
16 | def __init__(self, core):
17 | self.userName = None
18 | self.nickName = None
19 | self.updateLock = Lock()
20 | self.memberList = ContactList()
21 | self.mpList = ContactList()
22 | self.chatroomList = ContactList()
23 | self.msgList = Queue(-1)
24 | self.lastInputUserName = None
25 | self.memberList.set_default_value(contactClass=User)
26 | self.memberList.core = core
27 | self.mpList.set_default_value(contactClass=MassivePlatform)
28 | self.mpList.core = core
29 | self.chatroomList.set_default_value(contactClass=Chatroom)
30 | self.chatroomList.core = core
31 | def dumps(self):
32 | return {
33 | 'userName' : self.userName,
34 | 'nickName' : self.nickName,
35 | 'memberList' : self.memberList,
36 | 'mpList' : self.mpList,
37 | 'chatroomList' : self.chatroomList,
38 | 'lastInputUserName' : self.lastInputUserName, }
39 | def loads(self, j):
40 | self.userName = j.get('userName', None)
41 | self.nickName = j.get('nickName', None)
42 | del self.memberList[:]
43 | for i in j.get('memberList', []):
44 | self.memberList.append(i)
45 | del self.mpList[:]
46 | for i in j.get('mpList', []):
47 | self.mpList.append(i)
48 | del self.chatroomList[:]
49 | for i in j.get('chatroomList', []):
50 | self.chatroomList.append(i)
51 | # I tried to solve everything in pickle
52 | # but this way is easier and more storage-saving
53 | for chatroom in self.chatroomList:
54 | if 'MemberList' in chatroom:
55 | for member in chatroom['MemberList']:
56 | member.core = chatroom.core
57 | member.chatroom = chatroom
58 | if 'Self' in chatroom:
59 | chatroom['Self'].core = chatroom.core
60 | chatroom['Self'].chatroom = chatroom
61 | self.lastInputUserName = j.get('lastInputUserName', None)
62 | def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
63 | wechatAccount=None):
64 | with self.updateLock:
65 | if (name or userName or remarkName or nickName or wechatAccount) is None:
66 | return copy.deepcopy(self.memberList[0]) # my own account
67 | elif userName: # return the only userName match
68 | for m in self.memberList:
69 | if m['UserName'] == userName:
70 | return copy.deepcopy(m)
71 | else:
72 | matchDict = {
73 | 'RemarkName' : remarkName,
74 | 'NickName' : nickName,
75 | 'Alias' : wechatAccount, }
76 | for k in ('RemarkName', 'NickName', 'Alias'):
77 | if matchDict[k] is None:
78 | del matchDict[k]
79 | if name: # select based on name
80 | contact = []
81 | for m in self.memberList:
82 | if any([m.get(k) == name for k in ('RemarkName', 'NickName', 'Alias')]):
83 | contact.append(m)
84 | else:
85 | contact = self.memberList[:]
86 | if matchDict: # select again based on matchDict
87 | friendList = []
88 | for m in contact:
89 | if all([m.get(k) == v for k, v in matchDict.items()]):
90 | friendList.append(m)
91 | return copy.deepcopy(friendList)
92 | else:
93 | return copy.deepcopy(contact)
94 | def search_chatrooms(self, name=None, userName=None):
95 | with self.updateLock:
96 | if userName is not None:
97 | for m in self.chatroomList:
98 | if m['UserName'] == userName:
99 | return copy.deepcopy(m)
100 | elif name is not None:
101 | matchList = []
102 | for m in self.chatroomList:
103 | if name in m['NickName']:
104 | matchList.append(copy.deepcopy(m))
105 | return matchList
106 | def search_mps(self, name=None, userName=None):
107 | with self.updateLock:
108 | if userName is not None:
109 | for m in self.mpList:
110 | if m['UserName'] == userName:
111 | return copy.deepcopy(m)
112 | elif name is not None:
113 | matchList = []
114 | for m in self.mpList:
115 | if name in m['NickName']:
116 | matchList.append(copy.deepcopy(m))
117 | return matchList
118 |
--------------------------------------------------------------------------------
/itchat/utils.py:
--------------------------------------------------------------------------------
1 | import re, os, sys, subprocess, copy, traceback, logging
2 |
3 | try:
4 | from HTMLParser import HTMLParser
5 | except ImportError:
6 | from html.parser import HTMLParser
7 | try:
8 | from urllib import quote as _quote
9 | quote = lambda n: _quote(n.encode('utf8', 'replace'))
10 | except ImportError:
11 | from urllib.parse import quote
12 |
13 | import requests
14 |
15 | from . import config
16 |
17 | logger = logging.getLogger('itchat')
18 |
19 | emojiRegex = re.compile(r'')
20 | htmlParser = HTMLParser()
21 | try:
22 | b = u'\u2588'
23 | sys.stdout.write(b + '\r')
24 | sys.stdout.flush()
25 | except UnicodeEncodeError:
26 | BLOCK = 'MM'
27 | else:
28 | BLOCK = b
29 | friendInfoTemplate = {}
30 | for k in ('UserName', 'City', 'DisplayName', 'PYQuanPin', 'RemarkPYInitial', 'Province',
31 | 'KeyWord', 'RemarkName', 'PYInitial', 'EncryChatRoomId', 'Alias', 'Signature',
32 | 'NickName', 'RemarkPYQuanPin', 'HeadImgUrl'):
33 | friendInfoTemplate[k] = ''
34 | for k in ('UniFriend', 'Sex', 'AppAccountFlag', 'VerifyFlag', 'ChatRoomId', 'HideInputBarFlag',
35 | 'AttrStatus', 'SnsFlag', 'MemberCount', 'OwnerUin', 'ContactFlag', 'Uin',
36 | 'StarFriend', 'Statues'):
37 | friendInfoTemplate[k] = 0
38 | friendInfoTemplate['MemberList'] = []
39 |
40 | def clear_screen():
41 | os.system('cls' if config.OS == 'Windows' else 'clear')
42 |
43 | def emoji_formatter(d, k):
44 | ''' _emoji_deebugger is for bugs about emoji match caused by wechat backstage
45 | like :face with tears of joy: will be replaced with :cat face with tears of joy:
46 | '''
47 | def _emoji_debugger(d, k):
48 | s = d[k].replace('') # fix missing bug
50 | def __fix_miss_match(m):
51 | return '' % ({
52 | '1f63c': '1f601', '1f639': '1f602', '1f63a': '1f603',
53 | '1f4ab': '1f616', '1f64d': '1f614', '1f63b': '1f60d',
54 | '1f63d': '1f618', '1f64e': '1f621', '1f63f': '1f622',
55 | }.get(m.group(1), m.group(1)))
56 | return emojiRegex.sub(__fix_miss_match, s)
57 | def _emoji_formatter(m):
58 | s = m.group(1)
59 | if len(s) == 6:
60 | return ('\\U%s\\U%s'%(s[:2].rjust(8, '0'), s[2:].rjust(8, '0'))
61 | ).encode('utf8').decode('unicode-escape', 'replace')
62 | elif len(s) == 10:
63 | return ('\\U%s\\U%s'%(s[:5].rjust(8, '0'), s[5:].rjust(8, '0'))
64 | ).encode('utf8').decode('unicode-escape', 'replace')
65 | else:
66 | return ('\\U%s'%m.group(1).rjust(8, '0')
67 | ).encode('utf8').decode('unicode-escape', 'replace')
68 | d[k] = _emoji_debugger(d, k)
69 | d[k] = emojiRegex.sub(_emoji_formatter, d[k])
70 |
71 | def msg_formatter(d, k):
72 | emoji_formatter(d, k)
73 | d[k] = d[k].replace('
', '\n')
74 | d[k] = htmlParser.unescape(d[k])
75 |
76 | def check_file(fileDir):
77 | try:
78 | with open(fileDir):
79 | pass
80 | return True
81 | except:
82 | return False
83 |
84 | def print_qr(fileDir):
85 | if config.OS == 'Darwin':
86 | subprocess.call(['open', fileDir])
87 | elif config.OS == 'Linux':
88 | subprocess.call(['xdg-open', fileDir])
89 | else:
90 | os.startfile(fileDir)
91 |
92 | def print_cmd_qr(qrText, white=BLOCK, black=' ', enableCmdQR=True):
93 | blockCount = int(enableCmdQR)
94 | if abs(blockCount) == 0:
95 | blockCount = 1
96 | white *= abs(blockCount)
97 | if blockCount < 0:
98 | white, black = black, white
99 | sys.stdout.write(' '*50 + '\r')
100 | sys.stdout.flush()
101 | qr = qrText.replace('0', white).replace('1', black)
102 | sys.stdout.write(qr)
103 | sys.stdout.flush()
104 |
105 | def struct_friend_info(knownInfo):
106 | member = copy.deepcopy(friendInfoTemplate)
107 | for k, v in copy.deepcopy(knownInfo).items(): member[k] = v
108 | return member
109 |
110 | def search_dict_list(l, key, value):
111 | ''' Search a list of dict
112 | * return dict with specific value & key '''
113 | for i in l:
114 | if i.get(key) == value:
115 | return i
116 |
117 | def print_line(msg, oneLine = False):
118 | if oneLine:
119 | sys.stdout.write(' '*40 + '\r')
120 | sys.stdout.flush()
121 | else:
122 | sys.stdout.write('\n')
123 | sys.stdout.write(msg.encode(sys.stdin.encoding or 'utf8', 'replace'
124 | ).decode(sys.stdin.encoding or 'utf8', 'replace'))
125 | sys.stdout.flush()
126 |
127 | def test_connect(retryTime=5):
128 | for i in range(retryTime):
129 | try:
130 | r = requests.get(config.BASE_URL)
131 | return True
132 | except:
133 | if i == retryTime - 1:
134 | logger.error(traceback.format_exc())
135 | return False
136 |
137 | def contact_deep_copy(core, contact):
138 | with core.storageClass.updateLock:
139 | return copy.deepcopy(contact)
140 |
141 | def get_image_postfix(data):
142 | data = data[:20]
143 | if b'GIF' in data:
144 | return 'gif'
145 | elif b'PNG' in data:
146 | return 'png'
147 | elif b'JFIF' in data:
148 | return 'jpg'
149 | return ''
150 |
151 | def update_info_dict(oldInfoDict, newInfoDict):
152 | ''' only normal values will be updated here
153 | because newInfoDict is normal dict, so it's not necessary to consider templates
154 | '''
155 | for k, v in newInfoDict.items():
156 | if any((isinstance(v, t) for t in (tuple, list, dict))):
157 | pass # these values will be updated somewhere else
158 | elif oldInfoDict.get(k) is None or v not in (None, '', '0', 0):
159 | oldInfoDict[k] = v
160 |
--------------------------------------------------------------------------------
/docs/tutorial/tutorial0.md:
--------------------------------------------------------------------------------
1 | ## 一、课程介绍
2 |
3 | 本文最佳阅读方式为[实验楼的会员课程][vip],建议点击链接在实验楼内阅读。
4 |
5 | ### 1. 课程来源
6 |
7 | 关于itchat进一步使用的问题你可以在[主页][itchat]加入官方交流群也可以在课程下面向我提问。
8 |
9 | 课程在常见的三种系统中都可以进行操作,itchat建议使用可以安装的最新版本。
10 |
11 | 本课程通过聊天机器人为例,介绍如何使用Python完成微信的点对点信息交互。
12 |
13 | ### 2. 内容简介
14 |
15 | * 课程实现微信个人号聊天机器人
16 | * 通过自定义消息处理方法加入聊天功能
17 |
18 | ### 3. 课程知识点
19 |
20 | 本课程项目完成过程中将学习:
21 |
22 | * 微信消息的基本获取与处理
23 | * 微信消息的指定发送
24 |
25 | 其中将重点介绍微信消息的获取与处理。
26 |
27 | ## 二、实验环境
28 |
29 | 在终端中输入以下命令,完成微信的API包itchat的安装。
30 |
31 | 我们这里使用python3的环境(python2也是可行的):
32 |
33 | ```bash
34 | sudo pip3 install itchat --upgrade
35 | ```
36 |
37 | 通过该命令判断是否安装成功:
38 |
39 | ```bash
40 | python3 -c "import itchat"
41 | ```
42 |
43 | 如果没有报错信息说明你已经将实验环境安装完成。
44 |
45 | ![install][install]
46 |
47 | ## 三、实验原理
48 |
49 | 通过微信的Python接口itchat获取微信消息。
50 |
51 | 将微信消息传输到机器人接口(这里以图灵为例),获取机器人的返回消息。
52 |
53 | 将返回消息返回给微信消息的发送人。
54 |
55 | 实现将微信个人号变为聊天机器人的目的。
56 |
57 | ## 四、实验步骤
58 |
59 | ### 0. 基础知识
60 |
61 | 为了照顾一些从未使用过Python的新用户与使用其他语言的用户,这里简单的讲一下以下的代码如何使用。
62 |
63 | 下面的每一段描述都给出了相应的测试代码,如果没有特殊说明这段代码可以这样使用:
64 |
65 | 打开桌面的Xfce终端,先将目录通过以下命令切到桌面。
66 |
67 | ```bash
68 | cd Desktop
69 | ```
70 |
71 | 之后使用gedit编辑器编辑我们的主程序。
72 |
73 | 你也完全可以使用vim,会使用vim的话想必也知道这里应该输入什么命令了。
74 |
75 | ```bash
76 | gedit test.py
77 | ```
78 |
79 | 最后将给出的代码复制进编辑器,保存并退出,使用如下命令就可以使用了。
80 |
81 | ```bash
82 | python3 test.py
83 | ```
84 |
85 | 那么,就让我们开始正式进入Python操作微信的探索之旅吧。
86 |
87 | ### 1. 实现微信消息的获取
88 |
89 | itchat的注册时根据类型注册的。
90 |
91 | 在获取相应类型的信息时会调用该函数。
92 |
93 | 我们现在只需要获取最简单的文本消息,那么只需要这样注册:
94 |
95 | ```python
96 | import itchat
97 |
98 | @itchat.msg_register(itchat.content.TEXT)
99 | def print_content(msg):
100 | print(msg['Text'])
101 |
102 | itchat.auto_login()
103 | itchat.run()
104 | ```
105 |
106 | 其中第三行即注册的操作,通过装饰符将`print_content`注册为处理文本消息的函数。
107 |
108 | 微信有各种类型的数据,例如图片、语音、名片、分享等,也对应不同的注册参数:
109 |
110 | * 图片对应`itchat.content.PICTURE`
111 | * 语音对应`itchat.content.RECORDING`
112 | * 名片对应`itchat.content.CARD`
113 | * 其余的这里就不一一列举,更具体的内容可以自行搜索itchat阅读[文档][document]
114 |
115 | 执行命令
116 | ```
117 | python3 test.py
118 | ```
119 |
120 | 就可看到我们开始登陆微信:
121 |
122 | ![login][login]
123 |
124 | 扫码完成以后最基础的文本信息的接收就完成了,你可以尝试用他人的微信给自己发一条信息。
125 |
126 | 如果你不想要每次运行程序都扫码,可以在登陆命令中进行设置:
127 |
128 | ```python
129 | itchat.auto_login(hotReload=True)
130 | ```
131 |
132 | ### 2. 实现微信消息的发送
133 |
134 | 微信可以发送各类消息,文本、图片、文件等,不过我们现在只需要使用文本的发送。
135 |
136 | 其余的消息的发送有兴趣可以自行阅读。
137 |
138 | ```python
139 | itchat.send('Message Content', 'toUserName')
140 | ```
141 |
142 | 该发送消息的函数需要两个参数,消息的内容与接受者的UserName,即标识符。
143 |
144 | 那么我们试着向文件传输助手发送一条消息:
145 |
146 | ```python
147 | #coding=utf8
148 | import itchat
149 |
150 | itchat.auto_login(hotReload=True)
151 |
152 | # 注意实验楼环境的中文输入切换
153 | itchat.send(u'测试消息发送', 'filehelper')
154 | ```
155 |
156 | 打开手机看一下是否就完成了消息的发送。
157 |
158 | 保存代码后,执行命令:
159 |
160 | ```
161 | python3 test.py
162 | ```
163 |
164 | 扫描登录后的效果如下:
165 |
166 | ![send-hello][send-hello]
167 |
168 | 当然,还有一种更加快捷的回复方法就是在注册函数中直接回复。
169 |
170 | 例如下面的例子将会将文本消息原封不动的返回。
171 |
172 | ```python
173 | import itchat
174 |
175 | @itchat.msg_register(itchat.content.TEXT)
176 | def print_content(msg):
177 | return msg['Text']
178 |
179 | itchat.auto_login()
180 | itchat.run()
181 | ```
182 |
183 | 这种方式显然更加直观也更加简单(不需要输入接受者的UserName)
184 |
185 | 我们本次实践将会采用这种方式。
186 |
187 | ### 3. 实现最简单的与图灵机器人的交互
188 |
189 | 要做一个能够与人交流的机器人有很多种方法,最简单的莫过于使用他人提供的接口。
190 |
191 | 我们这里以图灵机器人为例,演示这一功能。
192 |
193 | 图灵机器人简单而言就是以一定的规则给图灵的服务器发送数据包(包含你对他说的话)
194 |
195 | 图灵的服务器会以一定的规则给你返回数据包(包含他回复你的话)
196 |
197 | 你需要一个Tuling Key来告诉图灵服务器你有权和他对话,我这里免费提供一些:
198 |
199 | ```bash
200 | 8edce3ce905a4c1dbb965e6b35c3834d
201 | eb720a8970964f3f855d863d24406576
202 | 1107d5601866433dba9599fac1bc0083
203 | 71f28bf79c820df10d39b4074345ef8c
204 | ```
205 |
206 | 下面我做一个配置图灵机器人的简单介绍,你想要自行了解或者申请Tuling Key可以看[这里][tuling]
207 |
208 | 发送的规则简而言之是这样的:
209 |
210 | ```json
211 | {
212 | 'key' : 'TULING_KEY',
213 | 'info' : 'YOUR_MSG',
214 | 'userid' : 'USERID',
215 | }
216 | ```
217 |
218 | 其中userId是用户的标志,让机器人知道你是你。(也就是一个Tuling Key可以有多个用户)
219 |
220 | 而返回的内容基本是这样的:
221 |
222 | ```json
223 | {
224 | 'code': 0,
225 | 'text': 'RETURN_MSG',
226 | }
227 | ```
228 |
229 | 我们需要的内容就在text键里面。
230 |
231 | 这里我们使用requests包完成整个操作(已经包含在itchat包的安装中了)。
232 |
233 | 最后值得一提的就是这是一个post请求,那么直接上代码应该比我絮絮叨叨的说要直观很多。
234 |
235 | ```python
236 | #coding=utf8
237 | import requests
238 |
239 | apiUrl = 'http://www.tuling123.com/openapi/api'
240 | data = {
241 | 'key' : '8edce3ce905a4c1dbb965e6b35c3834d', # 如果这个Tuling Key不能用,那就换一个
242 | 'info' : 'hello', # 这是我们发出去的消息
243 | 'userid' : 'wechat-robot', # 这里你想改什么都可以
244 | }
245 | # 我们通过如下命令发送一个post请求
246 | r = requests.post(apiUrl, data=data).json()
247 |
248 | # 让我们打印一下返回的值,看一下我们拿到了什么
249 | print(r)
250 | ```
251 |
252 | 我们可以看到他回复了你好。
253 |
254 | ![reply-hello][reply-hello]
255 |
256 | 至此我们已经理解并掌握了所有需要的内容,下面将其组装起来即可。
257 |
258 | ## 五、实验程序
259 |
260 | 我先从概念上说一下组装是一个怎么样的过程。
261 |
262 | 当然,如果你觉得代码更直观,我也在代码中为你写好了注释。
263 |
264 | 这里我们首先将与图灵服务器的交互定义为一个函数。
265 |
266 | 我们需要这个函数接收我们要发送给图灵的消息,返回图灵返回给我们的消息。
267 |
268 | 再将与图灵交互并返回图灵返回结果的操作写成函数并在itchat中注册。
269 |
270 | 最后启动itchat,我们的程序就完成了。
271 |
272 | ```python
273 | #coding=utf8
274 | import requests
275 | import itchat
276 |
277 | KEY = '8edce3ce905a4c1dbb965e6b35c3834d'
278 |
279 | def get_response(msg):
280 | # 这里我们就像在“3. 实现最简单的与图灵机器人的交互”中做的一样
281 | # 构造了要发送给服务器的数据
282 | apiUrl = 'http://www.tuling123.com/openapi/api'
283 | data = {
284 | 'key' : KEY,
285 | 'info' : msg,
286 | 'userid' : 'wechat-robot',
287 | }
288 | try:
289 | r = requests.post(apiUrl, data=data).json()
290 | # 字典的get方法在字典没有'text'值的时候会返回None而不会抛出异常
291 | return r.get('text')
292 | # 为了防止服务器没有正常响应导致程序异常退出,这里用try-except捕获了异常
293 | # 如果服务器没能正常交互(返回非json或无法连接),那么就会进入下面的return
294 | except:
295 | # 将会返回一个None
296 | return
297 |
298 | # 这里是我们在“1. 实现微信消息的获取”中已经用到过的同样的注册方法
299 | @itchat.msg_register(itchat.content.TEXT)
300 | def tuling_reply(msg):
301 | # 为了保证在图灵Key出现问题的时候仍旧可以回复,这里设置一个默认回复
302 | defaultReply = 'I received: ' + msg['Text']
303 | # 如果图灵Key出现问题,那么reply将会是None
304 | reply = get_response(msg['Text'])
305 | # a or b的意思是,如果a有内容,那么返回a,否则返回b
306 | # 有内容一般就是指非空或者非None,你可以用`if a: print('True')`来测试
307 | return reply or defaultReply
308 |
309 | # 为了让实验过程更加方便(修改程序不用多次扫码),我们使用热启动
310 | itchat.auto_login(hotReload=True)
311 | itchat.run()
312 | ```
313 |
314 | ## 六、实验结果
315 |
316 | 在本机上通过如下命令可以运行该程序
317 |
318 | ```bash
319 | python3 main.py
320 | ```
321 |
322 | 扫码登陆后程序就成功运行了。
323 |
324 | 之后在手机上使用别的账号给自己的微信号发送消息即可获得机器人的回复。
325 |
326 | 这里给出使用的效果图:
327 |
328 | ![demo][demo]
329 |
330 | 如果你想要通过与其他用户的交互完成该操作,自行在注册的函数中进行修改即可。
331 |
332 | 如果你想要进一步了解使用Python控制微信的细节,你也可以去到项目主页[itchat][itchat]。
333 |
334 | 或者直接阅读[文档][document]也是不错的选择。
335 |
336 | 如果你的本地环境并非Python3也没有关系,itchat同样完美支持Python2。
337 |
338 | ## 七、代码获取
339 |
340 | 我将整个项目目录做了一个打包,你可以直接下载后运行。
341 |
342 | 你可以在[这里][code-package]下载。
343 |
344 | 如果有什么问题,欢迎在我的[主页][author]留言或者[邮件][email]联系我。
345 |
346 | [vip]: https://www.shiyanlou.com/courses/684
347 | [author]: https://github.com/littlecodersh
348 | [install]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/install.png?imageView/2/h/300/
349 | [tuling]: http://tuling123.com/help/h_cent_webapi.jhtml
350 | [login]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/login.png?imageView/2/h/300/
351 | [send-hello]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/send-hello.png?imageView/2/h/400
352 | [reply-hello]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/reply-hello.png?imageView/2/h/300/
353 | [demo]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/demo.png?imageView/2/h/400/
354 | [code-package]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/main.py
355 | [email]: mailto:i7meavnktqegm1b@qq.com
356 | [itchat]: https://github.com/littlecodersh/itchat
357 | [document]: http://itchat.readthedocs.io/zh/latest/
358 |
--------------------------------------------------------------------------------
/docs/tutorial/tutorial2.md:
--------------------------------------------------------------------------------
1 | # 手把手教你扩展个人微信号(2)
2 |
3 | 现在的日常生活已经离不开微信,本文将会抛砖引玉演示如何使用Python调用微信API做一些有意思的东西。
4 |
5 | 看完这一系列教程,你就能从头开始实现自己关于微信的想法。
6 |
7 | 本文为教程的第二部分,主要以微信控制器、群发助手、好友删除检测为例演示如何调用微信API。
8 |
9 | Python基础并不困难,所以即使没有这方面基础辅助搜索引擎也完全可以学习本教程。
10 |
11 | 关于本教程有任何建议或者疑问,都欢迎邮件与我联系(i7meavnktqegm1b@qq.com),或者在[github][main-page]上提出。
12 |
13 | ## 教程流程简介
14 |
15 | 这一系列教程从如何分析微信协议开始,[第一部分][tutoral#1]教你如何从零开始获取并模拟扩展个人微信号所需要的协议。
16 |
17 | 第二部分将会就这些协议进行利用,以各项目为例介绍一些微信有意思功能的实现。
18 |
19 | 第三部分就协议的一些高级用法进行介绍,对框架做进一步介绍与扩展。
20 |
21 | 本文为教程的第二部分。
22 |
23 | ## 简单成果展示
24 |
25 | 完成了本文的学习,你将会完成三个小项目:(出于方便二次阅读,括号中都放上了源码链接)
26 |
27 | * 通过微信操作的音乐播放器([源码][demo-pcmusicviawechat])
28 | * 消息内容与对象可自定义的消息群发助手([源码][demo-wechatsmartwish])
29 | * 特定好友删除检测([源码][demo-wechatcheckfriend])
30 |
31 | 使用微信协议完成机器人较为平常,如果对具体细节感兴趣,可以添加个人号`littlecodersh`并回复“源代码”。
32 |
33 | 本文主要基于微信API的第三方包itchat,你可以在[项目主页][main-page]获取更多信息。
34 |
35 | ## 本部分所需环境
36 |
37 | 本文是这一教程的第二部分,需要基本的pip可用的Python环境。
38 |
39 | 本教程使用的环境如下:
40 |
41 | * Windows 8.1 (其他平台也可用)
42 | * Python 2 or 3
43 | * 微信版本6.3.25
44 |
45 | ## 微信控制器
46 |
47 | ![pic0][demo-pcmusicviawechat-0] ![pic1][demo-pcmusicviawechat-1]
48 |
49 | 在项目主页上,专门有人就微信作为智能家居入口向我提出了很多想法。
50 |
51 | 如果微信可以作为控制器,就可以不必自制手机端客户端的麻烦。
52 |
53 | 其实这个需求实现起来非常简单,这里我借鉴了yaphone的[RasWxMusicbox][yaphone-raswxmusicbox],使用了其中部分的代码作为演示。
54 |
55 | 这是一个通过微信控制电脑播放音乐的小项目,那么主要就是三个功能:
56 | * 输入“帮助”,显示帮助
57 | * 输入“关闭”,关闭音乐播放
58 | * 输入具体歌名,进入歌曲的选择
59 |
60 | 换成代码就是这样一个逻辑:
61 | ```python
62 | if msg == u'关闭':
63 | close_music()
64 | print(u'音乐已关闭')
65 | if msg == u'帮助':
66 | print(u'帮助信息')
67 | else:
68 | print(interact_select_song(msg))
69 | ```
70 |
71 | 那么现在需要解决的就是如何关闭音乐,如何选择音乐和如何使用微信交互。
72 |
73 | 关闭音乐我们这里使用打开空文件的方式,而选择音乐我们使用网易云音乐的API完成:
74 | ```python
75 | import os
76 | # 通过该命令安装该API: pip install NetEaseMusicApi
77 | from NetEaseMusicApi import interact_select_song
78 |
79 | with open('stop.mp3', 'w') as f: pass
80 | def close_music():
81 | os.startfile('stop.mp3')
82 | ```
83 |
84 | 而微信的调用可以通过itchat包简单的完成,这里要注意的是:
85 |
86 | * 有些账号无法与自己通信,所以我们选择与文件传输助手(filehelper)通信
87 | * 为了防止对于其他消息的响应,我们在第一行过滤了无关信息
88 | * itchat.run的选项分别为允许热拔插,方便调试
89 |
90 | ```python
91 | # 接上段程序
92 | # 通过该命令安装该API: pip install itchat
93 | import itchat
94 |
95 | @itchat.msg_register(itchat.content.TEXT)
96 | def music_player(msg):
97 | if msg['ToUserName'] != 'filehelper': return
98 | if msg['Text'] == u'关闭':
99 | close_music()
100 | itchat.send(u'音乐已关闭', 'filehelper')
101 | if msg['Text'] == u'帮助':
102 | itchat.send(u'帮助信息', 'filehelper')
103 | else:
104 | itchat.send(interact_select_song(msg['Text']), 'filehelper')
105 |
106 | itchat.auto_login(True)
107 | itchat.send(HELP_MSG, 'filehelper')
108 | itchat.run()
109 | ```
110 |
111 | itchat对常用功能都做好了封装,调用还是非常容易的。
112 |
113 | 完整的程序我放在了[gist][demo-pcmusicviawechat]上面,使用时不要忘记安装第三方包。
114 |
115 | 通过与文件传输助手的交互,微信就能够轻松变成其他程序的入口。
116 |
117 | ## 群发助手
118 |
119 | 在短信的时代,逢年过节都会需要接收和发送大量的短信。
120 |
121 | 虽然自己也看到短信就烦,但不发又怕会错过什么。
122 |
123 | 所以当时就产生了各式各样的群发工具,最简单的比如在消息中加入昵称,让人感觉不像群发。
124 |
125 | 不过可惜的是,微信自带的群发助手真的只是群发。
126 |
127 | 当然,稍加操作,一切皆有可能。
128 |
129 | 例如在消息中加入昵称:
130 |
131 | * 通过`get_friends`方法可以轻松获取所有的好友(好友首位是自己)
132 | * 基于不同的好友可以发送不同的消息
133 | * 这条程序运行后是真的会发消息出去,如果只是演示目的,把`itchat.send`改为`print`即可
134 |
135 | ```python
136 | #coding=utf8
137 | import itchat, time
138 |
139 | itchat.auto_login(True)
140 |
141 | SINCERE_WISH = u'祝%s新年快乐!'
142 |
143 | friendList = itchat.get_friends(update=True)[1:]
144 | for friend in friendList:
145 | # 如果是演示目的,把下面的方法改为print即可
146 | itchat.send(SINCERE_WISH % (friend['DisplayName']
147 | or friend['NickName']), friend['UserName'])
148 | time.sleep(.5)
149 | ```
150 |
151 | 又例如给特定的人发送特定的消息。
152 |
153 | 我们这里通过群聊实现,划定一个群聊,在群聊内则私信发送祝福。
154 |
155 | * 如果仅是创建群聊不说话,对方是不会有提示的
156 | * 群聊如果不**保存到通讯录**,是无法在各设备之间同步的(所以itchat也无法读取到)
157 | * 群聊在被获取的时候不会自带用户列表,所以需要使用`update_chatroom`更新用户列表
158 | * 当然,如果只是演示目的,把`itchat.send`改为`print`即可
159 |
160 | ```python
161 | #coding=utf8
162 | import itchat, time
163 |
164 | itchat.auto_login(True)
165 |
166 | REAL_SINCERE_WISH = u'祝%s新年快乐!!'
167 |
168 | chatroomName='wishgroup'
169 | itchat.get_chatrooms(update=True)
170 | chatrooms = itchat.search_chatrooms(name=chatroomName)
171 | if chatrooms is None:
172 | print(u'没有找到群聊:' + chatroomName)
173 | else:
174 | chatroom = itchat.update_chatroom(chatrooms[0]['UserName'])
175 | for friend in chatroom['MemberList']:
176 | friend = itchat.search_friends(userName=friend['UserName'])
177 | # 如果是演示目的,把下面的方法改为print即可
178 | itchat.send(REAL_SINCERE_WISH % (friend['DisplayName']
179 | or friend['NickName']), friend['UserName'])
180 | time.sleep(.5)
181 | ```
182 |
183 | 所以我的通讯录里会有从来不用的客户群、教师群什么的。
184 |
185 | 完整的程序我放在了[gist][demo-wechatsmartwish]上面,使用时不要忘记安装第三方包。
186 |
187 | 当然,为了防止误操作,完整程序中我把所有的`itchat.send`换成了`print`。
188 |
189 | 另外,不只有文字可以发送,文件、图片也都是可行的,具体操作见itchat的[文档][document]了。
190 |
191 | itchat获取微信可以获取到的各种内容也都非常方便。
192 |
193 | 其余的例如生日,节日什么的就看具体需求了。
194 |
195 | ## 好友删除检测
196 |
197 | ![pic][demo-wechatcheckfriend-0]
198 |
199 | 有时候我们会想知道某个好友有没有删除自己或者把自己拉入黑名单。
200 |
201 | 这一操作使用itchat也会变的非常简单。
202 |
203 | 原理的话,在于将好友拉入群聊时,非好友和黑名单好友不会被拉入群聊。
204 |
205 | 所以群聊的返回值中就有了好友与你关系的数据。
206 |
207 | 另外,群聊在第一次产生普通消息时才会被除创建者以外的人发现的(系统消息不算普通消息)。
208 |
209 | 这样,就可以隐蔽的完成好友检测。
210 |
211 | 写成代码的话,这个操作就是这样的:(只是演示,不能运行,运行版本在段末)
212 |
213 | ```python
214 | chatroomUserName = '@1234567'
215 | friend = itchat.get_friends()[1]
216 |
217 | r = itchat.add_member_into_chatroom(chatroomUserName, [friend])
218 | if r['BaseResponse']['ErrMsg'] == '':
219 | status = r['MemberList'][0]['MemberStatus']
220 | itchat.delete_member_from_chatroom(chatroom['UserName'], [friend])
221 | return { 3: u'该好友已经将你加入黑名单。',
222 | 4: u'该好友已经将你删除。', }.get(status,
223 | u'该好友仍旧与你是好友关系。')
224 | ```
225 |
226 | 其中,通过`add_member_into_chatroom`操作获取我们需要的返回值,即可得到好友的状态。
227 |
228 | 同样的,这次我们也将文件传输助手作为终端,具体方法与控制器一节类似。
229 |
230 | 这次我们确定的交互方式是接收“名片”消息,并判断名片中的好友与自己的关系。
231 |
232 | 那么获取名片信息的内容可以这么写:
233 |
234 | ```python
235 | import itchat
236 |
237 | @itchat.msg_register(itchat.content.CARD)
238 | def get_friend(msg):
239 | if msg['ToUserName'] != 'filehelper': return
240 | friendStatus = get_friend_status(msg['RecommendInfo'])
241 | itchat.send(friendStatus['NickName'], 'filehelper')
242 |
243 | itchat.auto_login(True)
244 | itchat.run()
245 | ```
246 |
247 | 那么我们所需要的所有部分就都解决了,下面将他们组合起来即可。
248 |
249 | 完整的程序我放在了[gist][demo-wechatcheckfriend]上面,使用时不要忘记安装第三方包。
250 |
251 | 在网页版微信的接口受到限制之前完全可以批量进行这一操作,检测哪些好友删除了自己。
252 |
253 | 但目前显然操作存在频率限制,所以只能做一些变通了。
254 |
255 | ## 之后的内容
256 |
257 | 到这里这一篇文章的主要内容就结束了。
258 |
259 | 主要从微信作为终端使用、自定义的消息交互、微信协议研究三方面开了一个简单的头。
260 |
261 | 其余有一些过于大众,如机器人,就不再赘述。
262 |
263 | 而另一些,需要一定的基础或者不适合分享,就留给各位自行研究。
264 |
265 | 如果要留个悬念,可以想象添加好友的方法status传2,轻松实现好友病毒式扩张。
266 |
267 | 利用微信的API可以做很多事情,文档我放在[这里][document],祝好运!
268 |
269 | ## 结束语
270 |
271 | 希望读完这篇文章能对你有帮助,有什么不足之处万望指正(鞠躬)。
272 |
273 | 有什么想法或者想要关注我的更新,欢迎来[**Github**](https://github.com/littlecodersh/ItChat)上***Star***或者***Fork***。
274 |
275 | 160928
276 |
277 | LittleCoder
278 |
279 | EOF
280 |
281 |
282 | [document]: http://itchat.readthedocs.io/zh/latest/
283 | [main-page]: https://github.com/littlecodersh/ItChat
284 | [tutoral#1]: http://python.jobbole.com/84918/
285 | [yaphone-raswxmusicbox]: https://github.com/yaphone/RasWxMusicbox
286 | [demo-pcmusicviawechat]: https://gist.github.com/littlecodersh/8468afbbb8d34c0c0e6848b6f9009c4c
287 | [demo-wechatsmartwish]: https://gist.github.com/littlecodersh/ae13ed93e0e8f3c820226fc6871f436d
288 | [demo-wechatcheckfriend]: https://gist.github.com/littlecodersh/3fef7d2afb2d502e4735be083c9f79e1
289 | [demo-pcmusicviawechat-0]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/Tutorial/2/demo-pcmusicviawechat-0.png?imageView/2/w/200/
290 | [demo-pcmusicviawechat-1]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/Tutorial/2/demo-pcmusicviawechat-1.png?imageView/2/w/200/
291 | [demo-wechatcheckfriend-0]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/Tutorial/2/demo-wechatcheckfriend.png?imageView/2/w/200/
292 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # itchat
2 |
3 | ![py27][py27] ![py35][py35]
4 |
5 | itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。
6 |
7 | 使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。
8 |
9 | 当然,该api的使用远不止一个机器人,更多的功能等着你来发现,比如[这些][tutorial2]。
10 |
11 | 该接口与公众号接口[itchatmp][itchatmp]共享类似的操作方式,学习一次掌握两个工具。
12 |
13 | 如今微信已经成为了个人社交的很大一部分,希望这个项目能够帮助你扩展你的个人的微信号、方便自己的生活。
14 |
15 | ## 安装
16 |
17 | 可以通过本命令安装itchat:
18 |
19 | ```python
20 | pip install itchat
21 | ```
22 |
23 | ## 简单入门实例
24 |
25 | 有了itchat,如果你想要给文件传输助手发一条信息,只需要这样:
26 |
27 | ```python
28 | import itchat
29 |
30 | itchat.auto_login()
31 |
32 | itchat.send('Hello, filehelper', toUserName='filehelper')
33 | ```
34 |
35 | 如果你想要回复发给自己的文本消息,只需要这样:
36 |
37 | ```python
38 | import itchat
39 |
40 | @itchat.msg_register(itchat.content.TEXT)
41 | def text_reply(msg):
42 | return msg.text
43 |
44 | itchat.auto_login()
45 | itchat.run()
46 | ```
47 |
48 | 一些进阶应用可以在下面的开源机器人的源码和进阶应用中看到,或者你也可以阅览[文档][document]。
49 |
50 | ## 试一试
51 |
52 | 这是一个基于这一项目的[开源小机器人][robot-source-code],百闻不如一见,有兴趣可以尝试一下。
53 |
54 | ![QRCode][robot-qr]
55 |
56 | ## 截屏
57 |
58 | ![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
59 |
60 | ## 进阶应用
61 |
62 | ### 特殊的字典使用方式
63 |
64 | 通过打印itchat的用户以及注册消息的参数,可以发现这些值都是字典。
65 |
66 | 但实际上itchat精心构造了相应的消息、用户、群聊、公众号类。
67 |
68 | 其所有的键值都可以通过这一方式访问:
69 |
70 | ```python
71 | @itchat.msg_register(TEXT)
72 | def _(msg):
73 | # equals to print(msg['FromUserName'])
74 | print(msg.fromUserName)
75 | ```
76 |
77 | 属性名为键值首字母小写后的内容。
78 |
79 | ```python
80 | author = itchat.search_friends(nickName='LittleCoder')[0]
81 | author.send('greeting, littlecoder!')
82 | ```
83 |
84 | ### 各类型消息的注册
85 |
86 | 通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
87 |
88 | ```python
89 | import itchat, time
90 | from itchat.content import *
91 |
92 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
93 | def text_reply(msg):
94 | msg.user.send('%s: %s' % (msg.type, msg.text))
95 |
96 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
97 | def download_files(msg):
98 | msg.download(msg.fileName)
99 | typeSymbol = {
100 | PICTURE: 'img',
101 | VIDEO: 'vid', }.get(msg.type, 'fil')
102 | return '@%s@%s' % (typeSymbol, msg.fileName)
103 |
104 | @itchat.msg_register(FRIENDS)
105 | def add_friend(msg):
106 | msg.user.verify()
107 | msg.user.send('Nice to meet you!')
108 |
109 | @itchat.msg_register(TEXT, isGroupChat=True)
110 | def text_reply(msg):
111 | if msg.isAt:
112 | msg.user.send(u'@%s\u2005I received: %s' % (
113 | msg.actualNickName, msg.text))
114 |
115 | itchat.auto_login(True)
116 | itchat.run(True)
117 | ```
118 |
119 | ### 命令行二维码
120 |
121 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
122 |
123 | ```python
124 | itchat.auto_login(enableCmdQR=True)
125 | ```
126 |
127 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
128 |
129 | ```python
130 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
131 | itchat.auto_login(enableCmdQR=2)
132 | ```
133 |
134 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
135 |
136 | ```python
137 | itchat.auto_login(enableCmdQR=-1)
138 | ```
139 |
140 | ### 退出程序后暂存登陆状态
141 |
142 | 通过如下命令登陆,即使程序关闭,一定时间内重新开启也可以不用重新扫码。
143 |
144 | ```python
145 | itchat.auto_login(hotReload=True)
146 | ```
147 |
148 | ### 用户搜索
149 |
150 | 使用`search_friends`方法可以搜索用户,有四种搜索方式:
151 | 1. 仅获取自己的用户信息
152 | 2. 获取特定`UserName`的用户信息
153 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
154 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
155 |
156 | 其中三、四项可以一同使用,下面是示例程序:
157 |
158 | ```python
159 | # 获取自己的用户信息,返回自己的属性字典
160 | itchat.search_friends()
161 | # 获取特定UserName的用户信息
162 | itchat.search_friends(userName='@abcdefg1234567')
163 | # 获取任何一项等于name键值的用户
164 | itchat.search_friends(name='littlecodersh')
165 | # 获取分别对应相应键值的用户
166 | itchat.search_friends(wechatAccount='littlecodersh')
167 | # 三、四项功能可以一同使用
168 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
169 | ```
170 |
171 | 关于公众号、群聊的获取与搜索在文档中有更加详细的介绍。
172 |
173 | ### 附件的下载与发送
174 |
175 | itchat的附件下载方法存储在msg的Text键中。
176 |
177 | 发送的文件的文件名(图片给出的默认文件名)都存储在msg的FileName键中。
178 |
179 | 下载方法接受一个可用的位置参数(包括文件名),并将文件相应的存储。
180 |
181 | ```python
182 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
183 | def download_files(msg):
184 | msg.download(msg.fileName)
185 | itchat.send('@%s@%s' % (
186 | 'img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']),
187 | msg['FromUserName'])
188 | return '%s received' % msg['Type']
189 | ```
190 |
191 | 如果你不需要下载到本地,仅想要读取二进制串进行进一步处理可以不传入参数,方法将会返回图片的二进制串。
192 |
193 | ```python
194 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
195 | def download_files(msg):
196 | with open(msg.fileName, 'wb') as f:
197 | f.write(msg.download())
198 | ```
199 |
200 | ### 用户多开
201 |
202 | 使用如下命令可以完成多开的操作:
203 |
204 | ```python
205 | import itchat
206 |
207 | newInstance = itchat.new_instance()
208 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
209 |
210 | @newInstance.msg_register(TEXT)
211 | def reply(msg):
212 | return msg.text
213 |
214 | newInstance.run()
215 | ```
216 |
217 | ### 退出及登陆完成后调用特定方法
218 |
219 | 登陆完成后的方法需要赋值在`loginCallback`中。
220 |
221 | 而退出后的方法需要赋值在`exitCallback`中。
222 |
223 | ```python
224 | import time
225 |
226 | import itchat
227 |
228 | def lc():
229 | print('finish login')
230 | def ec():
231 | print('exit')
232 |
233 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
234 | time.sleep(3)
235 | itchat.logout()
236 | ```
237 |
238 | 若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。
239 |
240 | ## 常见问题与解答
241 |
242 | Q: 为什么中文的文件没有办法上传?
243 |
244 | A: 这是由于`requests`的编码问题导致的。若需要支持中文文件传输,将[fields.py][fields.py-2](py3版本见[这里][fields.py-3])文件放入requests包的packages/urllib3下即可
245 |
246 | Q: 如何通过这个包将自己的微信号变为控制器?
247 |
248 | A: 有两种方式:发送、接受自己UserName的消息;发送接收文件传输助手(filehelper)的消息
249 |
250 | Q: 为什么我发送信息的时候部分信息没有成功发出来?
251 |
252 | A: 有些账号是天生无法给自己的账号发送信息的,建议使用`filehelper`代替。
253 |
254 | ## 作者
255 |
256 | [LittleCoder][littlecodersh]: 构架及维护Python2 Python3版本。
257 |
258 | [tempdban][tempdban]: 协议、构架及日常维护。
259 |
260 | [Chyroc][Chyroc]: 完成第一版本的Python3构架。
261 |
262 | ## 类似项目
263 |
264 | [youfou/wxpy][youfou-wxpy]: 优秀的api包装和配套插件,微信机器人/优雅的微信个人号API
265 |
266 | [liuwons/wxBot][liuwons-wxBot]: 类似的基于Python的微信机器人
267 |
268 | [zixia/wechaty][zixia-wechaty]: 基于Javascript(ES6)的微信个人账号机器人NodeJS框架/库
269 |
270 | [sjdy521/Mojo-Weixin][Mojo-Weixin]: 使用Perl语言编写的微信客户端框架,可通过插件提供基于HTTP协议的api接口供其他语言调用
271 |
272 | [HanSon/vbot][HanSon-vbot]: 基于PHP7的微信个人号机器人,通过实现匿名函数可以方便地实现各种自定义的功能
273 |
274 | ## 问题和建议
275 |
276 | 如果有什么问题或者建议都可以在这个[Issue][issue#1]和我讨论
277 |
278 | 或者也可以在gitter上交流:[![Gitter][gitter-picture]][gitter]
279 |
280 | 当然也可以加入我们新建的QQ群讨论:549762872
281 |
282 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
283 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
284 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
285 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
286 | [english-version]: https://github.com/littlecodersh/ItChat/blob/master/README_EN.md
287 | [itchatmp]: https://github.com/littlecodersh/itchatmp
288 | [document]: https://itchat.readthedocs.org/zh/latest/
289 | [tutorial2]: http://python.jobbole.com/86532/
290 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
291 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
292 | [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/
293 | [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/
294 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
295 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
296 | [littlecodersh]: https://github.com/littlecodersh
297 | [tempdban]: https://github.com/tempdban
298 | [Chyroc]: https://github.com/Chyroc
299 | [youfou-wxpy]: https://github.com/youfou/wxpy
300 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
301 | [zixia-wechaty]: https://github.com/zixia/wechaty
302 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
303 | [HanSon-vbot]: https://github.com/hanson/vbot
304 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
305 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # itchat
2 |
3 | [![Gitter][gitter-picture]][gitter] ![py27][py27] ![py35][py35] [English version][english-version]
4 |
5 | itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。
6 |
7 | 使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。
8 |
9 | 当然,该api的使用远不止一个机器人,更多的功能等着你来发现,比如[这些][tutorial2]。
10 |
11 | 该接口与公众号接口[itchatmp][itchatmp]共享类似的操作方式,学习一次掌握两个工具。
12 |
13 | 如今微信已经成为了个人社交的很大一部分,希望这个项目能够帮助你扩展你的个人的微信号、方便自己的生活。
14 |
15 | ## 安装
16 |
17 | 可以通过本命令安装itchat:
18 |
19 | ```python
20 | pip install itchat
21 | ```
22 |
23 | ## 简单入门实例
24 |
25 | 有了itchat,如果你想要给文件传输助手发一条信息,只需要这样:
26 |
27 | ```python
28 | import itchat
29 |
30 | itchat.auto_login()
31 |
32 | itchat.send('Hello, filehelper', toUserName='filehelper')
33 | ```
34 |
35 | 如果你想要回复发给自己的文本消息,只需要这样:
36 |
37 | ```python
38 | import itchat
39 |
40 | @itchat.msg_register(itchat.content.TEXT)
41 | def text_reply(msg):
42 | return msg.text
43 |
44 | itchat.auto_login()
45 | itchat.run()
46 | ```
47 |
48 | 一些进阶应用可以在下面的开源机器人的源码和进阶应用中看到,或者你也可以阅览[文档][document]。
49 |
50 | ## 试一试
51 |
52 | 这是一个基于这一项目的[开源小机器人][robot-source-code],百闻不如一见,有兴趣可以尝试一下。
53 |
54 | 由于好友数量实在增长过快,自动通过好友验证的功能演示暂时关闭。
55 |
56 | ![QRCode][robot-qr]
57 |
58 | ## 截屏
59 |
60 | ![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
61 |
62 | ## 进阶应用
63 |
64 | ### 特殊的字典使用方式
65 |
66 | 通过打印itchat的用户以及注册消息的参数,可以发现这些值都是字典。
67 |
68 | 但实际上itchat精心构造了相应的消息、用户、群聊、公众号类。
69 |
70 | 其所有的键值都可以通过这一方式访问:
71 |
72 | ```python
73 | @itchat.msg_register(TEXT)
74 | def _(msg):
75 | # equals to print(msg['FromUserName'])
76 | print(msg.fromUserName)
77 | ```
78 |
79 | 属性名为键值首字母小写后的内容。
80 |
81 | ```python
82 | author = itchat.search_friends(nickName='LittleCoder')[0]
83 | author.send('greeting, littlecoder!')
84 | ```
85 |
86 | ### 各类型消息的注册
87 |
88 | 通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
89 |
90 | ```python
91 | import itchat, time
92 | from itchat.content import *
93 |
94 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
95 | def text_reply(msg):
96 | msg.user.send('%s: %s' % (msg.type, msg.text))
97 |
98 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
99 | def download_files(msg):
100 | msg.download(msg.fileName)
101 | typeSymbol = {
102 | PICTURE: 'img',
103 | VIDEO: 'vid', }.get(msg.type, 'fil')
104 | return '@%s@%s' % (typeSymbol, msg.fileName)
105 |
106 | @itchat.msg_register(FRIENDS)
107 | def add_friend(msg):
108 | msg.user.verify()
109 | msg.user.send('Nice to meet you!')
110 |
111 | @itchat.msg_register(TEXT, isGroupChat=True)
112 | def text_reply(msg):
113 | if msg.isAt:
114 | msg.user.send(u'@%s\u2005I received: %s' % (
115 | msg.actualNickName, msg.text))
116 |
117 | itchat.auto_login(True)
118 | itchat.run(True)
119 | ```
120 |
121 | ### 命令行二维码
122 |
123 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
124 |
125 | ```python
126 | itchat.auto_login(enableCmdQR=True)
127 | ```
128 |
129 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
130 |
131 | ```python
132 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
133 | itchat.auto_login(enableCmdQR=2)
134 | ```
135 |
136 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
137 |
138 | ```python
139 | itchat.auto_login(enableCmdQR=-1)
140 | ```
141 |
142 | ### 退出程序后暂存登陆状态
143 |
144 | 通过如下命令登陆,即使程序关闭,一定时间内重新开启也可以不用重新扫码。
145 |
146 | ```python
147 | itchat.auto_login(hotReload=True)
148 | ```
149 |
150 | ### 用户搜索
151 |
152 | 使用`search_friends`方法可以搜索用户,有四种搜索方式:
153 | 1. 仅获取自己的用户信息
154 | 2. 获取特定`UserName`的用户信息
155 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
156 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
157 |
158 | 其中三、四项可以一同使用,下面是示例程序:
159 |
160 | ```python
161 | # 获取自己的用户信息,返回自己的属性字典
162 | itchat.search_friends()
163 | # 获取特定UserName的用户信息
164 | itchat.search_friends(userName='@abcdefg1234567')
165 | # 获取任何一项等于name键值的用户
166 | itchat.search_friends(name='littlecodersh')
167 | # 获取分别对应相应键值的用户
168 | itchat.search_friends(wechatAccount='littlecodersh')
169 | # 三、四项功能可以一同使用
170 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
171 | ```
172 |
173 | 关于公众号、群聊的获取与搜索在文档中有更加详细的介绍。
174 |
175 | ### 附件的下载与发送
176 |
177 | itchat的附件下载方法存储在msg的Text键中。
178 |
179 | 发送的文件的文件名(图片给出的默认文件名)都存储在msg的FileName键中。
180 |
181 | 下载方法接受一个可用的位置参数(包括文件名),并将文件相应的存储。
182 |
183 | ```python
184 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
185 | def download_files(msg):
186 | msg.download(msg.fileName)
187 | itchat.send('@%s@%s' % (
188 | 'img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']),
189 | msg['FromUserName'])
190 | return '%s received' % msg['Type']
191 | ```
192 |
193 | 如果你不需要下载到本地,仅想要读取二进制串进行进一步处理可以不传入参数,方法将会返回图片的二进制串。
194 |
195 | ```python
196 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
197 | def download_files(msg):
198 | with open(msg.fileName, 'wb') as f:
199 | f.write(msg.download())
200 | ```
201 |
202 | ### 用户多开
203 |
204 | 使用如下命令可以完成多开的操作:
205 |
206 | ```python
207 | import itchat
208 |
209 | newInstance = itchat.new_instance()
210 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
211 |
212 | @newInstance.msg_register(itchat.content.TEXT)
213 | def reply(msg):
214 | return msg.text
215 |
216 | newInstance.run()
217 | ```
218 |
219 | ### 退出及登陆完成后调用特定方法
220 |
221 | 登陆完成后的方法需要赋值在`loginCallback`中。
222 |
223 | 而退出后的方法需要赋值在`exitCallback`中。
224 |
225 | ```python
226 | import time
227 |
228 | import itchat
229 |
230 | def lc():
231 | print('finish login')
232 | def ec():
233 | print('exit')
234 |
235 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
236 | time.sleep(3)
237 | itchat.logout()
238 | ```
239 |
240 | 若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。
241 |
242 | ## 常见问题与解答
243 |
244 | Q: 如何通过这个包将自己的微信号变为控制器?
245 |
246 | A: 有两种方式:发送、接受自己UserName的消息;发送接收文件传输助手(filehelper)的消息
247 |
248 | Q: 为什么我发送信息的时候部分信息没有成功发出来?
249 |
250 | A: 有些账号是天生无法给自己的账号发送信息的,建议使用`filehelper`代替。
251 |
252 | ## 作者
253 |
254 | [LittleCoder][littlecodersh]: 构架及维护Python2 Python3版本。
255 |
256 | [tempdban][tempdban]: 协议、构架及日常维护。
257 |
258 | [Chyroc][Chyroc]: 完成第一版本的Python3构架。
259 |
260 | ## 类似项目
261 |
262 | [youfou/wxpy][youfou-wxpy]: 优秀的api包装和配套插件,微信机器人/优雅的微信个人号API
263 |
264 | [liuwons/wxBot][liuwons-wxBot]: 类似的基于Python的微信机器人
265 |
266 | [zixia/wechaty][zixia-wechaty]: 基于Javascript(ES6)的微信个人账号机器人NodeJS框架/库
267 |
268 | [sjdy521/Mojo-Weixin][Mojo-Weixin]: 使用Perl语言编写的微信客户端框架,可通过插件提供基于HTTP协议的api接口供其他语言调用
269 |
270 | [HanSon/vbot][HanSon-vbot]: 基于PHP7的微信个人号机器人,通过实现匿名函数可以方便地实现各种自定义的功能
271 |
272 | [yaphone/itchat4j][yaphone-itchat4j]: 用Java扩展个人微信号的能力
273 |
274 | [kanjielu/jeeves][kanjielu-jeeves]: 使用springboot开发的微信机器人
275 |
276 | ## 问题和建议
277 |
278 | 如果有什么问题或者建议都可以在这个[Issue][issue#1]和我讨论
279 |
280 | 或者也可以在gitter上交流:[![Gitter][gitter-picture]][gitter]
281 |
282 | 当然也可以加入我们新建的QQ群讨论:549762872, 205872856
283 |
284 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
285 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
286 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
287 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
288 | [english-version]: https://github.com/littlecodersh/ItChat/blob/master/README_EN.md
289 | [itchatmp]: https://github.com/littlecodersh/itchatmp
290 | [document]: https://itchat.readthedocs.org/zh/latest/
291 | [tutorial2]: http://python.jobbole.com/86532/
292 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
293 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
294 | [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/
295 | [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/
296 | [littlecodersh]: https://github.com/littlecodersh
297 | [tempdban]: https://github.com/tempdban
298 | [Chyroc]: https://github.com/Chyroc
299 | [youfou-wxpy]: https://github.com/youfou/wxpy
300 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
301 | [zixia-wechaty]: https://github.com/zixia/wechaty
302 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
303 | [HanSon-vbot]: https://github.com/hanson/vbot
304 | [yaphone-itchat4j]: https://github.com/yaphone/itchat4j
305 | [kanjielu-jeeves]: https://github.com/kanjielu/jeeves
306 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
307 |
--------------------------------------------------------------------------------
/docs/intro/messages.md:
--------------------------------------------------------------------------------
1 | # 消息内容
2 |
3 | ## 微信一般消息内容
4 |
5 | ```python
6 | {
7 | "FromUserName": "",
8 | "ToUserName": "",
9 | "Content": "",
10 | "StatusNotifyUserName": "",
11 | "ImgWidth": 0,
12 | "PlayLength": 0,
13 | "RecommendInfo": {},
14 | "StatusNotifyCode": 0,
15 | "NewMsgId": "",
16 | "Status": 0,
17 | "VoiceLength": 0,
18 | "ForwardFlag": 0,
19 | "AppMsgType": 0,
20 | "Ticket": "",
21 | "AppInfo": {},
22 | "Url": "",
23 | "ImgStatus": 0,
24 | "MsgType": 0,
25 | "ImgHeight": 0,
26 | "MediaId": "",
27 | "MsgId": "",
28 | "FileName": "",
29 | "HasProductId": 0,
30 | "FileSize": "",
31 | "CreateTime": 0,
32 | "SubMsgType": 0
33 | }
34 | ```
35 |
36 | 微信回复的所有消息都遵循这一格式,下面将就每种消息类型具体分析。
37 |
38 | ## 消息的具体内容
39 |
40 | 本段部分参考[Urinx的Github](https://github.com/Urinx/WeixinBot)并修改。
41 |
42 | ### 微信初始化消息
43 |
44 | ```
45 | MsgType: 51
46 | FromUserName: 自己ID
47 | ToUserName: 自己ID
48 | StatusNotifyUserName: 最近联系的联系人ID
49 | Content:
50 |
51 |
52 |
53 | # 最近联系的联系人
54 | filehelper,xxx@chatroom,wxid_xxx,xxx,...
55 |
56 |
57 |
58 |
59 | # 朋友圈
60 | MomentsUnreadMsgStatus
61 |
62 |
63 | 1454502365
64 |
65 |
66 |
67 |
68 | # 未读的功能账号消息,群发助手,漂流瓶等
69 |
70 |
71 |
72 | ```
73 |
74 | ### 文本消息
75 |
76 | ```
77 | MsgType: 1
78 | FromUserName: 发送方ID
79 | ToUserName: 接收方ID
80 | Content: 消息内容
81 | ```
82 |
83 | ### 图片消息
84 |
85 | ```
86 | MsgType: 3
87 | FromUserName: 发送方ID
88 | ToUserName: 接收方ID
89 | MsgId: 用于获取图片
90 | Content:
91 |
92 |
93 |
94 |
95 | ```
96 |
97 | itchat添加了Text键,键值为下载该图片的方法。
98 |
99 | ### 小视频消息
100 |
101 | ```
102 | MsgType: 62
103 | FromUserName: 发送方ID
104 | ToUserName: 接收方ID
105 | MsgId: 用于获取小视频
106 | Content:
107 |
108 |
109 |
110 |
111 | ```
112 |
113 | itchat添加了Text键,键值为下载该视频的方法。
114 |
115 | ### 地理位置消息
116 |
117 | ```
118 | MsgType: 1
119 | FromUserName: 发送方ID
120 | ToUserName: 接收方ID
121 | Content: http://weixin.qq.com/cgi-bin/redirectforward?args=xxx
122 | ```
123 |
124 | itchat添加了Text键,键值为该地点的文本形式。
125 |
126 | ### 名片消息
127 |
128 | ```
129 | MsgType: 42
130 | FromUserName: 发送方ID
131 | ToUserName: 接收方ID
132 | Content:
133 |
134 |
135 |
136 | RecommendInfo:
137 | {
138 | "UserName": "xxx", # ID
139 | "Province": "xxx",
140 | "City": "xxx",
141 | "Scene": 17,
142 | "QQNum": 0,
143 | "Content": "",
144 | "Alias": "xxx", # 微信号
145 | "OpCode": 0,
146 | "Signature": "",
147 | "Ticket": "",
148 | "Sex": 0, # 1:男, 2:女
149 | "NickName": "xxx", # 昵称
150 | "AttrStatus": 4293221,
151 | "VerifyFlag": 0
152 | }
153 | ```
154 |
155 | itchat添加了Text键,键值为该调用`add_friend`需要的属性。
156 |
157 | ### 语音消息
158 |
159 | ```
160 | MsgType: 34
161 | FromUserName: 发送方ID
162 | ToUserName: 接收方ID
163 | MsgId: 用于获取语音
164 | Content:
165 |
166 |
167 |
168 | ```
169 |
170 | itchat添加了Text键,键值为下载该语音文件的方法。
171 |
172 | ### 动画表情
173 |
174 | ```
175 | MsgType: 47
176 | FromUserName: 发送方ID
177 | ToUserName: 接收方ID
178 | Content:
179 |
180 |
181 |
182 |
183 | ```
184 |
185 | itchat添加了Text键,键值为下载该图片表情的方法。
186 |
187 | 由于版权问题,部分微信商店提供的表情是无法下载的,注意。
188 |
189 | ### 普通链接或应用分享消息
190 |
191 | ```
192 | MsgType: 49
193 | AppMsgType: 5
194 | FromUserName: 发送方ID
195 | ToUserName: 接收方ID
196 | Url: 链接地址
197 | FileName: 链接标题
198 | Content:
199 |
200 |
201 |
202 |
203 | 5
204 |
205 |
206 |
207 | ...
208 |
209 |
210 |
211 |
212 |
213 |
214 | ```
215 |
216 | ### 音乐链接消息
217 |
218 | ```
219 | MsgType: 49
220 | AppMsgType: 3
221 | FromUserName: 发送方ID
222 | ToUserName: 接收方ID
223 | Url: 链接地址
224 | FileName: 音乐名
225 |
226 | AppInfo: # 分享链接的应用
227 | {
228 | Type: 0,
229 | AppID: wx485a97c844086dc9
230 | }
231 |
232 | Content:
233 |
234 |
235 |
236 |
237 |
238 | 3
239 | 0
240 |
241 |
242 |
243 |
244 | 0
245 |
246 |
247 |
248 | http://ws.stream.qqmusic.qq.com/C100003i9hMt1bgui0.m4a?vkey=6867EF99F3684&guid=ffffffffc104ea2964a111cf3ff3edaf&fromtag=46
249 |
250 |
251 | http://ws.stream.qqmusic.qq.com/C100003i9hMt1bgui0.m4a?vkey=6867EF99F3684&guid=ffffffffc104ea2964a111cf3ff3edaf&fromtag=46
252 |
253 |
254 | 0
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | http://imgcache.qq.com/music/photo/album/63/180_albumpic_143163_0.jpg
265 |
266 |
267 |
268 |
269 | 0
270 |
271 | 29
272 | 摇一摇搜歌
273 |
274 |
275 |
276 | ```
277 |
278 | ### 群消息
279 |
280 | ```
281 | MsgType: 1
282 | FromUserName: @@xxx
283 | ToUserName: @xxx
284 | Content:
285 | @xxx:
xxx
286 | ```
287 |
288 | itchat增加了三个群聊相关的键值:
289 |
290 | * isAt: 判断是否@本号
291 | * ActualNickName: 实际NickName
292 | * Content: 实际Content
293 |
294 | ### 红包消息
295 |
296 | ```
297 | MsgType: 49
298 | AppMsgType: 2001
299 | FromUserName: 发送方ID
300 | ToUserName: 接收方ID
301 | Content: 未知
302 | ```
303 |
304 | ### 系统消息
305 |
306 | ```
307 | MsgType: 10000
308 | FromUserName: 发送方ID
309 | ToUserName: 自己ID
310 | Content:
311 | "你已添加了 xxx ,现在可以开始聊天了。"
312 | "如果陌生人主动添加你为朋友,请谨慎核实对方身份。"
313 | "收到红包,请在手机上查看"
314 | ```
315 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | itchat
2 | ======
3 |
4 | |Python2| |Python3|
5 |
6 | itchat is an open source api for WeChat, a commonly-used Chinese social networking app.
7 |
8 | Accessing your personal wechat account through itchat in python has never been easier.
9 |
10 | A wechat robot can handle all the basic messages with only less than 30 lines of codes.
11 |
12 | And it's similiar to itchatmp (api for wechat massive platform), learn once and get two tools.
13 |
14 | Now Wechat is an important part of personal life, hopefully this repo can help you extend your personal wechat account's functionality and enbetter user's experience with wechat.
15 |
16 | **Installation**
17 |
18 | .. code:: bash
19 |
20 | pip install itchat
21 |
22 | **Simple uses**
23 |
24 | With itchat, if you want to send a message to filehelper, this is how:
25 |
26 | .. code:: python
27 |
28 | import itchat
29 |
30 | itchat.auto_login()
31 |
32 | itchat.send('Hello, filehelper', toUserName='filehelper')
33 |
34 | And you only need to write this to reply personal text messages.
35 |
36 | .. code:: python
37 |
38 | import itchat
39 |
40 | @itchat.msg_register(itchat.content.TEXT)
41 | def text_reply(msg):
42 | return msg.text
43 |
44 | itchat.auto_login()
45 | itchat.run()
46 |
47 | For more advanced uses you may continue on reading or browse the `document `__.
48 |
49 | **Try**
50 |
51 | You may have a try of the robot based on this project first:
52 |
53 | |QRCodeOfRobot|
54 |
55 | Here is the `code `__.
56 |
57 | **Advanced uses**
58 |
59 | *Special usage of message dictionary*
60 |
61 | You may find out that all the users and messages of itchat are dictionaries by printing them out onto the screen.
62 |
63 | But actually they are useful classes itchat created.
64 |
65 | They have useful keys and useful interfaces, like:
66 |
67 | .. code:: python
68 |
69 | @itchat.msg_register(TEXT)
70 | def _(msg):
71 | # equals to print(msg['FromUserName'])
72 | print(msg.fromUserName)
73 |
74 | And like:
75 |
76 | .. code:: python
77 |
78 | author = itchat.search_friends(nickName='LittleCoder')[0]
79 | author.send('greeting, littlecoder!')
80 |
81 | *Message register of various types*
82 |
83 | The following is a demo of how itchat is configured to fetch and reply daily information.
84 |
85 | .. code:: python
86 |
87 | import itchat, time
88 | from itchat.content import *
89 |
90 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
91 | def text_reply(msg):
92 | msg.user.send('%s: %s' % (msg.type, msg.text))
93 |
94 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
95 | def download_files(msg):
96 | msg.download(msg.fileName)
97 | typeSymbol = {
98 | PICTURE: 'img',
99 | VIDEO: 'vid', }.get(msg.type, 'fil')
100 | return '@%s@%s' % (typeSymbol, msg.fileName)
101 |
102 | @itchat.msg_register(FRIENDS)
103 | def add_friend(msg):
104 | msg.user.verify()
105 | msg.user.send('Nice to meet you!')
106 |
107 | @itchat.msg_register(TEXT, isGroupChat=True)
108 | def text_reply(msg):
109 | if msg.isAt:
110 | msg.user.send(u'@%s\u2005I received: %s' % (
111 | msg.actualNickName, msg.text))
112 |
113 | itchat.auto_login(True)
114 | itchat.run(True)
115 |
116 | *Command line QR Code*
117 |
118 | You can access the QR Code in command line through using this command:
119 |
120 | .. code:: python
121 |
122 | itchat.auto_login(enableCmdQR=True)
123 |
124 | Because of width of some character differs from systems, you may adjust the enableCmdQR to fix the problem.
125 |
126 | .. code:: python
127 |
128 | # for some linux system, width of block character is one instead of two, so enableCmdQR should be 2
129 | itchat.auto_login(enableCmdQR=2)
130 |
131 | Default background color of command line is dark (black), if it's not, you may set enableCmdQR to be negative:
132 |
133 | .. code:: python
134 |
135 | itchat.auto_login(enableCmdQR=-1)
136 |
137 | *Hot reload*
138 |
139 | By using the following command, you may reload the program without re-scan QRCode in some time.
140 |
141 | .. code:: python
142 |
143 | itchat.auto_login(hotReload=True)
144 |
145 | *User search*
146 |
147 | By using `search_friends`, you have four ways to search a user:
148 |
149 | 1. Get your own user information
150 | 2. Get user information through `UserName`
151 | 3. Get user information whose remark name or wechat account or nickname matches name key of the function
152 | 4. Get user information whose remark name, wechat account and nickname match what are given to the function
153 |
154 | Way 3, 4 can be used together, the following is the demo program:
155 |
156 | .. code:: python
157 |
158 | # get your own user information
159 | itchat.search_friends()
160 | # get user information of specific username
161 | itchat.search_friends(userName='@abcdefg1234567')
162 | # get user information of function 3
163 | itchat.search_friends(name='littlecodersh')
164 | # get user information of function 4
165 | itchat.search_friends(wechatAccount='littlecodersh')
166 | # combination of way 3, 4
167 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
168 |
169 | There are detailed information about searching and getting of massive platforms and chatrooms in document.
170 |
171 | *Download and send attachments*
172 |
173 | The attachment download function of itchat is in Text key of msg
174 |
175 | Name of the file (default name of picture) is in FileName key of msg
176 |
177 | Download function accept one location value (include the file name) and store attachment accordingly.
178 |
179 | .. code:: python
180 |
181 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
182 | def download_files(msg):
183 | msg.download(msg.fileName)
184 | itchat.send('@%s@%s' % (
185 | 'img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']),
186 | msg['FromUserName'])
187 | return '%s received' % msg['Type']
188 |
189 | If you don't want a local copy of the picture, you may pass nothing to the function to get a binary string.
190 |
191 | .. code:: python
192 |
193 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
194 | def download_files(msg):
195 | with open(msg.fileName, 'wb') as f:
196 | f.write(msg.download())
197 |
198 | *Multi instance*
199 |
200 | You may use the following commands to open multi instance.
201 |
202 | .. code:: python
203 |
204 | import itchat
205 |
206 | newInstance = itchat.new_instance()
207 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
208 |
209 | @newInstance.msg_register(itchat.content.TEXT)
210 | def reply(msg):
211 | return msg['Text']
212 |
213 | newInstance.run()
214 |
215 | *Set callback after login and logout*
216 |
217 | Callback of login and logout are set through `loginCallback` and `exitCallback`.
218 |
219 | .. code:: python
220 |
221 | import time
222 |
223 | import itchat
224 |
225 | def lc():
226 | print('finish login')
227 | def ec():
228 | print('exit')
229 |
230 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
231 | time.sleep(3)
232 | itchat.logout()
233 |
234 | If loginCallback is not set, qr picture will be deleted and cmd will be cleared.
235 |
236 | If you exit through phone, exitCallback will also be called.
237 |
238 | **FAQ**
239 |
240 | Q: Why I can't send files whose name is encoded in utf8?
241 |
242 | A: That's because of the upload setting of requests, you can put `this file `__ (for py3 you need `this `__) into packages/urllib3 of requests package.
243 |
244 | Q: How to use this package to use my wechat as an monitor?
245 |
246 | A: There are two ways: communicate with your own account or with filehelper.
247 |
248 | Q: Why sometimes I can't send messages?
249 |
250 | A: Some account simply can't send messages to yourself, so use `filehelper` instead.
251 |
252 | **Comments**
253 |
254 | If you have any problems or suggestions, you can talk to me in this `issue `__
255 |
256 | Or on `gitter `__.
257 |
258 | .. |QRCodeOfRobot| image:: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/200/
259 | .. |Python2| image:: https://img.shields.io/badge/python-2.7-ff69b4.svg
260 | .. |Python3| image:: https://img.shields.io/badge/python-3.5-red.svg
261 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # itchat
2 |
3 | [![Gitter][gitter-picture]][gitter] ![py27][py27] ![py35][py35] [Chinese version][chinese-version]
4 |
5 | itchat is an open source api for WeChat, a commonly-used Chinese social networking app.
6 |
7 | Accessing your personal wechat account through itchat in python has never been easier.
8 |
9 | A wechat robot can handle all the basic messages with only less than 30 lines of codes.
10 |
11 | And it's similiar to itchatmp (api for wechat massive platform), learn once and get two tools.
12 |
13 | 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.
14 |
15 | ## Installation
16 |
17 | itchat can be installed with this little one-line command:
18 |
19 | ```python
20 | pip install itchat
21 | ```
22 |
23 | ## Simple uses
24 |
25 | With itchat, if you want to send a message to filehelper, this is how:
26 |
27 | ```python
28 | import itchat
29 |
30 | itchat.auto_login()
31 |
32 | itchat.send('Hello, filehelper', toUserName='filehelper')
33 | ```
34 |
35 | And you only need to write this to reply personal text messages.
36 |
37 | ```python
38 | import itchat
39 |
40 | @itchat.msg_register(itchat.content.TEXT)
41 | def text_reply(msg):
42 | return msg.text
43 |
44 | itchat.auto_login()
45 | itchat.run()
46 | ```
47 |
48 | For more advanced uses you may continue on reading or browse the [document][document].
49 |
50 | ## Have a try
51 |
52 | This QRCode is a wechat account based on the framework of [demo code][robot-source-code]. Seeing is believing, so have a try:)
53 |
54 | ![QRCode][robot-qr]
55 |
56 | ## Screenshots
57 |
58 | ![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
59 |
60 | ## Advanced uses
61 |
62 | ### Special usage of message dictionary
63 |
64 | You may find out that all the users and messages of itchat are dictionaries by printing them out onto the screen.
65 |
66 | But actually they are useful classes itchat created.
67 |
68 | They have useful keys and useful interfaces, like:
69 |
70 | ```python
71 | @itchat.msg_register(TEXT)
72 | def _(msg):
73 | # equals to print(msg['FromUserName'])
74 | print(msg.fromUserName)
75 | ```
76 |
77 | And like:
78 |
79 | ```python
80 | author = itchat.search_friends(nickName='LittleCoder')[0]
81 | author.send('greeting, littlecoder!')
82 | ```
83 |
84 | ### Message register of various types
85 |
86 | The following is a demo of how itchat is configured to fetch and reply daily information.
87 |
88 | ```python
89 | import itchat, time
90 | from itchat.content import *
91 |
92 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
93 | def text_reply(msg):
94 | msg.user.send('%s: %s' % (msg.type, msg.text))
95 |
96 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
97 | def download_files(msg):
98 | msg.download(msg.fileName)
99 | typeSymbol = {
100 | PICTURE: 'img',
101 | VIDEO: 'vid', }.get(msg.type, 'fil')
102 | return '@%s@%s' % (typeSymbol, msg.fileName)
103 |
104 | @itchat.msg_register(FRIENDS)
105 | def add_friend(msg):
106 | msg.user.verify()
107 | msg.user.send('Nice to meet you!')
108 |
109 | @itchat.msg_register(TEXT, isGroupChat=True)
110 | def text_reply(msg):
111 | if msg.isAt:
112 | msg.user.send(u'@%s\u2005I received: %s' % (
113 | msg.actualNickName, msg.text))
114 |
115 | itchat.auto_login(True)
116 | itchat.run(True)
117 | ```
118 |
119 | ### Command line QR Code
120 |
121 | You can access the QR Code in command line through using this command:
122 |
123 | ```python
124 | itchat.auto_login(enableCmdQR=True)
125 | ```
126 |
127 | Because of width of some character differs from systems, you may adjust the enableCmdQR to fix the problem.
128 |
129 | ```python
130 | # for some linux system, width of block character is one instead of two, so enableCmdQR should be 2
131 | itchat.auto_login(enableCmdQR=2)
132 | ```
133 |
134 | Default background color of command line is dark (black), if it's not, you may set enableCmdQR to be negative:
135 |
136 | ```python
137 | itchat.auto_login(enableCmdQR=-1)
138 | ```
139 |
140 | ### Hot reload
141 |
142 | By using the following command, you may reload the program without re-scan QRCode in some time.
143 |
144 | ```python
145 | itchat.auto_login(hotReload=True)
146 | ```
147 |
148 | ### User search
149 |
150 | By using `search_friends`, you have four ways to search a user:
151 | 1. Get your own user information
152 | 2. Get user information through `UserName`
153 | 3. Get user information whose remark name or wechat account or nickname matches name key of the function
154 | 4. Get user information whose remark name, wechat account and nickname match what are given to the function
155 |
156 | Way 3, 4 can be used together, the following is the demo program:
157 |
158 | ```python
159 | # get your own user information
160 | itchat.search_friends()
161 | # get user information of specific username
162 | itchat.search_friends(userName='@abcdefg1234567')
163 | # get user information of function 3
164 | itchat.search_friends(name='littlecodersh')
165 | # get user information of function 4
166 | itchat.search_friends(wechatAccount='littlecodersh')
167 | # combination of way 3, 4
168 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
169 | ```
170 |
171 | There is detailed information about searching and getting of massive platforms and chatrooms in document.
172 |
173 | ### Download and send attachments
174 |
175 | The attachment download function of itchat is in Text key of msg
176 |
177 | Name of the file (default name of picture) is in FileName key of msg
178 |
179 | Download function accept one location value (include the file name) and store attachment accordingly.
180 |
181 | ```python
182 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
183 | def download_files(msg):
184 | msg.download(msg.fileName)
185 | itchat.send('@%s@%s' % (
186 | 'img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']),
187 | msg['FromUserName'])
188 | return '%s received' % msg['Type']
189 | ```
190 |
191 | If you don't want a local copy of the picture, you may pass nothing to the function to get a binary string.
192 |
193 | ```python
194 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
195 | def download_files(msg):
196 | with open(msg.fileName, 'wb') as f:
197 | f.write(msg.download())
198 | ```
199 |
200 | ### Multi instance
201 |
202 | You may use the following commands to open multi instance.
203 |
204 | ```python
205 | import itchat
206 |
207 | newInstance = itchat.new_instance()
208 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
209 |
210 | @newInstance.msg_register(itchat.content.TEXT)
211 | def reply(msg):
212 | return msg['Text']
213 |
214 | newInstance.run()
215 | ```
216 |
217 | ### Set callback after login and logout
218 |
219 | Callback of login and logout are set through `loginCallback` and `exitCallback`.
220 |
221 | ```python
222 | import time
223 |
224 | import itchat
225 |
226 | def lc():
227 | print('finish login')
228 | def ec():
229 | print('exit')
230 |
231 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
232 | time.sleep(3)
233 | itchat.logout()
234 | ```
235 |
236 | If loginCallback is not set, qr picture will be deleted and cmd will be cleared.
237 |
238 | If you exit through phone, exitCallback will also be called.
239 |
240 | ## FAQ
241 |
242 | Q: How to use this package to use my wechat as an monitor?
243 |
244 | A: There are two ways: communicate with your own account or with filehelper.
245 |
246 | Q: Why sometimes I can't send messages?
247 |
248 | A: Some account simply can't send messages to yourself, so use `filehelper` instead.
249 |
250 | ## Author
251 |
252 | [LittleCoder][littlecodersh]: Structure and py2 py3 version
253 |
254 | [tempdban][tempdban]: Structure and daily maintainance
255 |
256 | [Chyroc][Chyroc]: first py3 version
257 |
258 | ## See also
259 |
260 | [liuwons/wxBot][liuwons-wxBot]: A wechat robot similiar to the robot branch
261 |
262 | [zixia/wechaty][zixia-wechaty]: Wechat for bot in Javascript(ES6), Personal Account Robot Framework/Library
263 |
264 | [sjdy521/Mojo-Weixin][Mojo-Weixin]: Wechat web api in Perl, available with HTTP requests
265 |
266 | [yaphone/itchat4j][yaphone-itchat4j]: Extend your wechat with java
267 |
268 | ## Comments
269 |
270 | If you have any problems or suggestions, feel free to put it up in this [Issue][issue#1].
271 |
272 | Or you may also use [![Gitter][gitter-picture]][gitter]
273 |
274 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
275 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
276 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
277 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
278 | [chinese-version]: https://github.com/littlecodersh/ItChat/blob/master/README.md
279 | [document]: https://itchat.readthedocs.org/zh/latest/
280 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
281 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
282 | [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/
283 | [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/
284 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
285 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
286 | [littlecodersh]: https://github.com/littlecodersh
287 | [tempdban]: https://github.com/tempdban
288 | [Chyroc]: https://github.com/Chyroc
289 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
290 | [zixia-wechaty]: https://github.com/zixia/wechaty
291 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
292 | [yaphone-itchat4j]: https://github.com/yaphone/itchat4j
293 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
294 |
--------------------------------------------------------------------------------
/docs/tutorial/tutorial1.md:
--------------------------------------------------------------------------------
1 | # 手把手教你扩展个人微信号(1)
2 |
3 | 现在的日常生活已经离不开微信,难免会生出微信有没有什么API可以使用的想法。
4 |
5 | 那样就可以拿自己微信做个消息聚合、开个投票什么的,可以显然没有这种东西。
6 |
7 | 不过还好,有网页版微信不就等于有了API么,这个项目就是出于这个想法出现的。
8 |
9 | 看完这一系列教程,你就能从头开始实现自己关于微信的想法,当然,欢迎使用我已经写好的基本框架。
10 |
11 | 本文为该教程的第一部分,主要讲述抓包与伪造,将会以最简单的方法介绍使用Python模拟登陆抓取数据等内容。
12 |
13 | Python与基本的网络基础都不困难,所以即使没有这方面基础辅助搜索引擎也完全可以学习本教程。
14 |
15 | 关于本教程有任何建议或者疑问,都欢迎邮件与我联系,或者在[github上提出](https://github.com/littlecodersh/ItChat)(i7meavnktqegm1b@qq.com)
16 |
17 | ## 教程流程简介
18 |
19 | 教程将会从如何分析微信协议开始,第一部分将教你如何从零开始获取并模拟扩展个人微信号所需要的协议。
20 |
21 | 第二部分将会就这些协议进行利用,以微信机器人为例介绍我给出的项目基本框架与存储、任务识别等功能。
22 |
23 | 第三部分就项目基本框架开发插件,以消息聚合等功能为例对框架做进一步介绍与扩展。
24 |
25 | ## 简单成果展示
26 |
27 | 目前的样例微信号被扩展为了能够完成信息上传下载的机器人,用于展示信息交互功能。
28 |
29 | 其支持文件、图片、语音的上传下载,可以扫码尝试使用。
30 |
31 | 
32 |
33 | ## 本部分所需环境
34 |
35 | 本文是这一教程的第一部分,需要配置抓包与Python环境。
36 |
37 | 本教程使用的环境如下:
38 |
39 | * Windows 8.1
40 | * Python 2.7.11 (安装Image, requests)
41 | * Wireshark 2.0.2
42 | * 微信版本6.3.15
43 |
44 | ### Wireshark配置
45 |
46 | Wireshark是常见的抓包软件,这里通过一些配置抓取微信网页端的流量。
47 |
48 | 由于微信网页端使用https,需要特殊的配置才能看到有意义的内容,具体的配置见[这里](http://jingyan.baidu.com/article/20b68a88b2af7f796cec62b3.html)。
49 |
50 | 配置完成以后开始抓包,载入`https://www.baidu.com`后若能看到http请求则配置成功。
51 |
52 | ## 分析并模拟扫码,并获取登录状态
53 |
54 | 微信网页端登陆分为很多步,这里以第一步扫码为例讲解如何从抓包开始完成模拟。
55 |
56 | ### 分析过程
57 |
58 | 在抓包以前,我们需要先想清楚这是一个什么样的过程。
59 |
60 | 我们都登录过网页端微信,没有的话可以现在做一个尝试:[微信网页端](https://wx.qq.com)。
61 |
62 | 这个过程简单而言可以分为如下几步:
63 |
64 | 1. 向服务器提供一些用于获取二维码的数据
65 | 1. 服务器返回二维码
66 | 1. 向服务器询问二维码扫描状态
67 | 1. 服务器返回扫描状态
68 |
69 | 有了这些概念以后就可以开始将这四步和包对应起来。
70 |
71 | ### 对应过程与实际的包
72 |
73 | 开启wireshark抓包后登陆网页端微信,完成扫码登陆,然后关闭wireshark抓包。
74 |
75 | 筛选http请求(就是菜单栏下面输入的那个http),可以看到这样的界面。
76 |
77 | 
78 |
79 | 这里需要讲的就是第一列“No.”列的数字就是后文说的几号包,例如第一行就是30号包。数据包的类型则在Info列中可以看到,是GET,POST或是别的请求。
80 |
81 | 那么我们可以开始分析抓到的包了,我们先粗略的浏览一下数据包。
82 |
83 | 第325号包引起了我的注意,因为登陆过程当中非常有特征的一个过程是二维码的获取,所以我们尝试打开这一数据包的图片的内容。
84 |
85 | 
86 |
87 | 325号包是由292号包的请求获取的,292号包又是一个普通的get请求,所以我们尝试直接在浏览器中访问这一网址。(访问自己抓到的网址)
88 |
89 | 具体的网址通过双击打开292号包即可找到。如需要可以点击[这里](http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FTutorial%2FQRCodeDetailPackage.png)看图。
90 |
91 | 我们发现直接在浏览器中获取了一张二维码,所以这很有可能就是上述一、二步的过程了。
92 |
93 | 那么我们是向服务器提供了哪些数据获取了二维码呢?
94 |
95 | * 每次我们登录的二维码会变化,且没有随二维码传回的标识,所以我们肯定提供了每次不同的信息
96 | * 网址中最后一部分看起来比较像标识:https://login.weixin.qq.com/qrcode/4ZtmDT6OPg==
97 | * 为了进一步验证猜想,再次抓包,发现类似292号包的请求url仅最后一部分存在区别
98 | * 所以我们提供了`4ZtmDT6Opg==`获取到了这一二维码。
99 |
100 | 那么这一标识是随机生成的还是服务器获取的呢?
101 |
102 | * 从最近的包开始分析服务器传回的数据(Source是服务器地址的数据),发现就在上一行,286号包有我们感兴趣的数据。
103 | * 打开这个包,可以看到其返回的数据为`window.QRLogin.code = 200; window.QRLogin.uuid = "4ZtmDT6OPg==";`(见下方截图)
104 | * 显然导致服务器返回这一请求的284号包就是我们获取标识(下称uuid)所需要伪造的包。
105 |
106 | 
107 |
108 | 那么284号包需要传递给服务器哪些数据?
109 |
110 | * 这是一个get请求,所以我们分析其请求的url:`https://login.weixin.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=en_US&_=1453725386008`。
111 | * 可以发现需要给出五个量`appid, redirect_uri, fun, lang, _`。
112 | * 其中除了appid其余都显然是固定的量(_的格式显然为时间戳)。
113 | * 然而搜索284号包之前的包也没有发现这一数值的来源,所以暂且认为其也是固定的量,模拟时如果出现问题再做尝试。
114 |
115 | 到了这里,1,2步的过程我们已经能够对应上相应的包了。
116 |
117 | 3,4部的最显著特征是在扫描成功以后会获取扫描用的微信号的头像。
118 |
119 | 我们还是首先大致的浏览一下服务器返回的数据包,试图找到包含图片的数据包。
120 |
121 | * 从325号包(微信头像肯定在二维码之后获取)开始浏览。
122 | * 我们发现338号包中包含一个base64加密的图片,[解压](http://www.vgot.net/test/image2base64.php?)后可以看到自己的头像。
123 | * 所以这个数据包就是服务器返回的扫描成功的数据包了,而前面那部分`window.code=201`显然就是表示状态的代码。(见下方截图)
124 | * 经过尝试与再次抓包,我们理解状态码的涵义:`200:登陆成功 201:扫描成功 408:图片过期`
125 | * 那么第四部我们已经能够完全的理解
126 |
127 | 
128 |
129 | 我们很容易的找到了在登录过程当中不断出现的请求,那么要怎么模拟呢?
130 |
131 | * 首先这是一个简单的get请求,url为:https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=4ZtmDT6OPg==&tip=1&r=-2026440414&_=1453725386009
132 | * 可以发现需要给出五个量`loginicon, uuid, tip, r, _`
133 | * 通过多次抓包发现除了r以外都可以找到简单的规律,那么r的规律等待模拟时再尝试处理
134 |
135 | 至此你应该已经能将四个过程全部与具体的数据包对应。为了避免有遗漏的过程,我们将没有使用到的与服务器交互的数据包标识出来(右键Mark)。经过简单的浏览,认为其中并没有必须的数据包交互。但值得注意的是,如果之后模拟数据包没有问题却无法登陆的话应当再回到这些数据包中搜寻。
136 |
137 | 这里做一个简单的小结,这一部分简单的介绍了分析数据包的基本思路,以及一些小的技巧。当然这些仅供参考,在具体的抓包中完全可以根据具体的交互过程自由发挥。而目前留下来的问题有:第一步时的appid与第三步时的r,留待模拟时在做研究。
138 |
139 | ### 使用Python模拟扫码
140 |
141 | 这一部分我们使用python的requests模块,可以通过`pip install requests`安装。
142 |
143 | 我们先来简单的讲述一下这个包。
144 | ```python
145 | import requests
146 | # 新建一个session对象(就像开了一个浏览器一样)
147 | session = requests.Session()
148 | # 使用get方法获取https://www.baidu.com/s?wd=python
149 | url = 'https://www.baidu.com/s'
150 | params = { 'wd': 'python', }
151 | r = session.get(url = url, params = params)
152 | with open('baidu.htm') as f: f.write(r.content) # 存入文件,可以使用浏览器尝试打开
153 | # 举例使用post方法
154 | import json
155 | url = 'https://www.baidu.com'
156 | data = { 'wd': 'python', }
157 | r = session.get(url = url, data = json.dumps(data))
158 | with open('baidu.htm') as f: f.write(r.content)
159 | # 以上代码与下面的代码不连续
160 | ```
161 |
162 | 如果想要更多的了解这个包,可以浏览[requests快速入门](http://blog.csdn.net/iloveyin/article/details/21444613)。
163 |
164 | 你可以尝试获取一个你熟悉的网站来测试使用requests,在测试时可以打开抓包,查看你发送的数据包与想要发送的数据包是否一样。
165 |
166 | 那么我们开始模拟第一、二个过程,向服务器提供一些用于获取二维码的数据,服务器返回二维码。
167 | * 向服务器提交284,292号包
168 | * 从服务器返回数据中提取出uuid与二维码图片
169 |
170 | **284号包**
171 |
172 | 
173 |
174 | 我们需要模拟的地址为:https://login.weixin.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=en_US&_=1453725386008 ,所以我们模拟的代码如下:
175 |
176 | ```python
177 | #coding=utf8
178 | import time, requests
179 | session = requests.Session()
180 | url = 'https://login.weixin.qq.com/jslogin'
181 | params = {
182 | 'appid': 'wx782c26e4c19acffb',
183 | 'redirect_uri': 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage',
184 | 'fun': 'new',
185 | 'lang': 'en_US',
186 | '_': int(time.time()),
187 | }
188 | r = session.get(url, params = params)
189 | print('Content: %s'%r.text)
190 | ```
191 |
192 | 当然,将模拟的地址全部写在url里面效果完全一样。
193 |
194 | 值得一提的是requests会帮我们自动urlencode,如果不需要urlencode(/变为了%2F)可以将所有内容都写在url里面。
195 |
196 | **提取出uuid**
197 |
198 | 这里使用re,如果不了解正则表达式的话可以直接拿来用,毕竟和这一个教程并不相关。
199 |
200 | ```python
201 | # 上接上一段程序
202 | import re
203 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";'
204 | # 我们可以看到返回的量是上述的格式,括号内的内容被提取了出来
205 | data = re.search(regx, r.text)
206 | if data and data.group(1) == '200': uuid = data.group(2)
207 | print('uuid: %s'%uuid)
208 | ```
209 |
210 | 如果没能成功获取到uuid可以尝试再运行一次。
211 |
212 | **292号包**
213 |
214 | 
215 |
216 | 我们需要模拟的url为:https://login.weixin.qq.com/qrcode/4ZtmDT6OPg== ,所以我们模拟的代码如下:
217 |
218 | ```python
219 | # 上接上一段程序
220 | url = 'https://login.weixin.qq.com/qrcode/' + uuid
221 | r = session.get(url, stream = True)
222 | with open('QRCode.jpg', 'wb') as f: f.write(r.content)
223 | # 现在你可以在你存储代码的位置发现一张存下来的图片,用下面的代码打开它
224 | import platform, os, subprocess
225 | if platform.system() == 'Darwin':
226 | subprocess.call(['open', 'QRCode.jpg'])
227 | elif platform.system() == 'Linux':
228 | subprocess.call(['xdg-open', 'QRCode.jpg'])
229 | else:
230 | os.startfile('QR.jpg')
231 | ```
232 |
233 | 由于我们需要获取图像,所以需要以二进制数据流的形式获取服务器返回的数据包,所以增加`stream = True`。
234 |
235 | 而将二进制数据流写入也需要在打开文件时设定二进制写入,即`open('QRCode.jpg', 'wb')`。
236 |
237 | 当然,如果获取失败可以再运行一次。
238 |
239 | 同理的三、四步也可以按照这个方法写出,这里就不再赘述,只给出代码。
240 |
241 | 而经过测试我们发现,第一步时的appid实际是一个固定的量,第三步时的r甚至不输入也可以登录。
242 |
243 | ```python
244 | # 上接上一段代码
245 | import time
246 |
247 | while 1:
248 | url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login'
249 | # 这里演示一下不使用自带的urlencode
250 | params = 'tip=1&uuid=%s&_=%s'%(uuid, int(time.time()))
251 | r = session.get(url, params = params)
252 | regx = r'window.code=(\d+)'
253 | data = re.search(regx, r.text)
254 | if not data: continue
255 | if data.group(1) == '200':
256 | # 下面一段是为了之后获取登录信息做准备
257 | uriRegex = r'window.redirect_uri="(\S+)";'
258 | redirectUri = re.search(uriRegex, r.text).group(1)
259 | r = session.get(redirectUri, allow_redirects=False)
260 | redirectUri = redirectUri[:redirectUri.rfind('/')]
261 | baseRequestText = r.text
262 | break
263 | elif data.group(1) == '201':
264 | print('You have scanned the QRCode')
265 | time.sleep(1)
266 | elif data.group(1) == '408':
267 | raise Exception('QRCode should be renewed')
268 | print('Login successfully')
269 | ```
270 |
271 | 当你看到**Login successfully**时,说明至此我们已经成功从零开始,通过抓包分析,用python成功模拟了python登陆。
272 |
273 | 不过是不是看上去没有什么反馈呢?那是因为我们还没有模拟会产生反馈的包,但其实差的只是研究发文字、发图片什么的包了。
274 |
275 | 为了体现我们已经登陆了,加上后面这段代码就可以看到登陆的账号信息:
276 |
277 | ```python
278 | # 上接上一段代码
279 | import xml.dom.minidom
280 | def get_login_info(s):
281 | baseRequest = {}
282 | for node in xml.dom.minidom.parseString(s).documentElement.childNodes:
283 | if node.nodeName == 'skey':
284 | baseRequest['Skey'] = node.childNodes[0].data.encode('utf8')
285 | elif node.nodeName == 'wxsid':
286 | baseRequest['Sid'] = node.childNodes[0].data.encode('utf8')
287 | elif node.nodeName == 'wxuin':
288 | baseRequest['Uin'] = node.childNodes[0].data.encode('utf8')
289 | elif node.nodeName == 'pass_ticket':
290 | baseRequest['DeviceID'] = node.childNodes[0].data.encode('utf8')
291 | return baseRequest
292 | baseRequest = get_login_info(baseRequestText)
293 |
294 | url = '%s/webwxinit?r=%s' % (redirectUri, int(time.time()))
295 | data = {
296 | 'BaseRequest': baseRequest,
297 | }
298 | headers = { 'ContentType': 'application/json; charset=UTF-8' }
299 | r = session.post(url, data = json.dumps(data), headers = headers)
300 | dic = json.loads(r.content.decode('utf-8', 'replace'))
301 |
302 | print('Log in as %s'%dic['User']['NickName'])
303 | ```
304 |
305 | 这里做一个简单的小结:
306 |
307 | * 首先需要用python初始化一个session,否则登录过程的存储将会比较麻烦。
308 | * 模拟数据包的时候首先区分get与post请求,对应session的get与post方法。
309 | * get的数据为url后半部分的内容,post是数据包最后一部分的内容。
310 | * get方法中传入数据的标示为params, post方法中传入数据的标示为data。
311 | * session的get,post方法返回一个量,可以通过r.text自动编码显示。
312 | * 存储图片有特殊的方式与配置。
313 |
314 | ## 小结
315 |
316 | 到现在为止我展示了一个完整的抓包、分析、模拟的过程完成了模拟登陆,其他一些事情其实也都是类似的过程,想清楚每一步要做些什么即可。
317 |
318 | 这里用到的软件都只介绍了最简单的一些方法,进一步的内容这里给出一些建议:
319 |
320 | * wireshark可以直接浏览官方文档,有空可以做一个了解。
321 | * requests包的使用通过搜索引擎即可,特殊的功能建议直接阅读源码。
322 |
323 | 那么做一个小练习好了,测试一下学到的东西:读取命令行的输入并发送给自己。(这部分的源码放在了文末)
324 |
325 | ## 具体运用时可能遇到的难点
326 |
327 | ### 命令行登录一段时间后无法与服务器正常交互
328 |
329 | 这是因为微信网页端存在心跳机制,一段时间不交互将会断开连接。
330 |
331 | 另外,每次获取数据时(webwxsync)记得更新SyncKey。
332 |
333 | ### 某个特定请求不知道如何模拟
334 |
335 | 在项目中已经模拟好了几乎所有的请求,你可以通过参考我的方法与数据包。
336 |
337 | 如果之后微信网页版出现更新我会在本项目中及时更新。
338 |
339 | 项目中的微信网页端接口见[这里](https://github.com/littlecodersh/ItChat/blob/master/itchat/client.py)
340 |
341 | ### 无法上传中文文件名的文件与图片
342 |
343 | 这是因为使用requests包会自动将中文文件名编码为服务器端无法识别的格式,所以需要修改requests包或者使用别的方法上传文件。
344 |
345 | 最简单的方法即将requests包的packages/urlib3中的fields.py中的`format_header_param`方法改为如下内容:
346 |
347 | ```python
348 | def format_header_param(name, value):
349 | if not any(ch in value for ch in '"\\\r\n'):
350 | result = '%s="%s"' % (name, value)
351 | try:
352 | result.encode('ascii')
353 | except UnicodeEncodeError:
354 | pass
355 | else:
356 | return result
357 | if not six.PY3: # Python 2:
358 | value = value.encode('utf-8')
359 | value = email.utils.encode_rfc2231(value, 'utf-8')
360 | value = '%s="%s"' % (name, value.decode('utf8'))
361 | return value
362 | ```
363 |
364 | ### 登录时出现不安全的提示
365 |
366 | 建议更新Python版本至2.7.11
367 |
368 | ## 小练习答案
369 |
370 | 源码可在该地址下载:[这里](https://github.com/littlecodersh/EasierLife/blob/master/Scripts/SendToMyself.py)
371 |
372 | ## 结束语
373 |
374 | 希望读完这篇文章能对你有帮助,有什么不足之处万望指正(鞠躬)。
375 |
376 | 有什么想法或者想要关注我的更新,欢迎来[**Github**](https://github.com/littlecodersh/ItChat)上***Star***或者***Fork***。
377 |
378 | 160318
379 |
380 | LittleCoder
381 |
382 | EOF
383 |
--------------------------------------------------------------------------------
/itchat/storage/templates.py:
--------------------------------------------------------------------------------
1 | import logging, copy, pickle
2 | from weakref import ref
3 |
4 | from ..returnvalues import ReturnValue
5 | from ..utils import update_info_dict
6 |
7 | logger = logging.getLogger('itchat')
8 |
9 | class AttributeDict(dict):
10 | def __getattr__(self, value):
11 | keyName = value[0].upper() + value[1:]
12 | try:
13 | return self[keyName]
14 | except KeyError:
15 | raise AttributeError("'%s' object has no attribute '%s'" % (
16 | self.__class__.__name__.split('.')[-1], keyName))
17 | def get(self, v, d=None):
18 | try:
19 | return self[v]
20 | except KeyError:
21 | return d
22 |
23 | class UnInitializedItchat(object):
24 | def _raise_error(self, *args, **kwargs):
25 | logger.warning('An itchat instance is called before initialized')
26 | def __getattr__(self, value):
27 | return self._raise_error
28 |
29 | class ContactList(list):
30 | ''' when a dict is append, init function will be called to format that dict '''
31 | def __init__(self, *args, **kwargs):
32 | super(ContactList, self).__init__(*args, **kwargs)
33 | self.__setstate__(None)
34 | @property
35 | def core(self):
36 | return getattr(self, '_core', lambda: fakeItchat)() or fakeItchat
37 | @core.setter
38 | def core(self, value):
39 | self._core = ref(value)
40 | def set_default_value(self, initFunction=None, contactClass=None):
41 | if hasattr(initFunction, '__call__'):
42 | self.contactInitFn = initFunction
43 | if hasattr(contactClass, '__call__'):
44 | self.contactClass = contactClass
45 | def append(self, value):
46 | contact = self.contactClass(value)
47 | contact.core = self.core
48 | if self.contactInitFn is not None:
49 | contact = self.contactInitFn(self, contact) or contact
50 | super(ContactList, self).append(contact)
51 | def __deepcopy__(self, memo):
52 | r = self.__class__([copy.deepcopy(v) for v in self])
53 | r.contactInitFn = self.contactInitFn
54 | r.contactClass = self.contactClass
55 | r.core = self.core
56 | return r
57 | def __getstate__(self):
58 | return 1
59 | def __setstate__(self, state):
60 | self.contactInitFn = None
61 | self.contactClass = User
62 | def __str__(self):
63 | return '[%s]' % ', '.join([repr(v) for v in self])
64 | def __repr__(self):
65 | return '<%s: %s>' % (self.__class__.__name__.split('.')[-1],
66 | self.__str__())
67 |
68 | class AbstractUserDict(AttributeDict):
69 | def __init__(self, *args, **kwargs):
70 | super(AbstractUserDict, self).__init__(*args, **kwargs)
71 | @property
72 | def core(self):
73 | return getattr(self, '_core', lambda: fakeItchat)() or fakeItchat
74 | @core.setter
75 | def core(self, value):
76 | self._core = ref(value)
77 | def update(self):
78 | return ReturnValue({'BaseResponse': {
79 | 'Ret': -1006,
80 | 'ErrMsg': '%s can not be updated' % \
81 | self.__class__.__name__, }, })
82 | def set_alias(self, alias):
83 | return ReturnValue({'BaseResponse': {
84 | 'Ret': -1006,
85 | 'ErrMsg': '%s can not set alias' % \
86 | self.__class__.__name__, }, })
87 | def set_pinned(self, isPinned=True):
88 | return ReturnValue({'BaseResponse': {
89 | 'Ret': -1006,
90 | 'ErrMsg': '%s can not be pinned' % \
91 | self.__class__.__name__, }, })
92 | def verify(self):
93 | return ReturnValue({'BaseResponse': {
94 | 'Ret': -1006,
95 | 'ErrMsg': '%s do not need verify' % \
96 | self.__class__.__name__, }, })
97 | def get_head_image(self, imageDir=None):
98 | return self.core.get_head_img(self.userName, picDir=imageDir)
99 | def delete_member(self, userName):
100 | return ReturnValue({'BaseResponse': {
101 | 'Ret': -1006,
102 | 'ErrMsg': '%s can not delete member' % \
103 | self.__class__.__name__, }, })
104 | def add_member(self, userName):
105 | return ReturnValue({'BaseResponse': {
106 | 'Ret': -1006,
107 | 'ErrMsg': '%s can not add member' % \
108 | self.__class__.__name__, }, })
109 | def send_raw_msg(self, msgType, content):
110 | return self.core.send_raw_msg(msgType, content, self.userName)
111 | def send_msg(self, msg='Test Message'):
112 | return self.core.send_msg(msg, self.userName)
113 | def send_file(self, fileDir, mediaId=None):
114 | return self.core.send_file(fileDir, self.userName, mediaId)
115 | def send_image(self, fileDir, mediaId=None):
116 | return self.core.send_image(fileDir, self.userName, mediaId)
117 | def send_video(self, fileDir=None, mediaId=None):
118 | return self.core.send_video(fileDir, self.userName, mediaId)
119 | def send(self, msg, mediaId=None):
120 | return self.core.send(msg, self.userName, mediaId)
121 | def search_member(self, name=None, userName=None, remarkName=None, nickName=None,
122 | wechatAccount=None):
123 | return ReturnValue({'BaseResponse': {
124 | 'Ret': -1006,
125 | 'ErrMsg': '%s do not have members' % \
126 | self.__class__.__name__, }, })
127 | def __deepcopy__(self, memo):
128 | r = self.__class__()
129 | for k, v in self.items():
130 | r[copy.deepcopy(k)] = copy.deepcopy(v)
131 | r.core = self.core
132 | return r
133 | def __str__(self):
134 | return '{%s}' % ', '.join(
135 | ['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
136 | def __repr__(self):
137 | return '<%s: %s>' % (self.__class__.__name__.split('.')[-1],
138 | self.__str__())
139 | def __getstate__(self):
140 | return 1
141 | def __setstate__(self, state):
142 | pass
143 |
144 | class User(AbstractUserDict):
145 | def __init__(self, *args, **kwargs):
146 | super(User, self).__init__(*args, **kwargs)
147 | self.__setstate__(None)
148 | def update(self):
149 | r = self.core.update_friend(self.userName)
150 | if r:
151 | update_info_dict(self, r)
152 | return r
153 | def set_alias(self, alias):
154 | return self.core.set_alias(self.userName, alias)
155 | def set_pinned(self, isPinned=True):
156 | return self.core.set_pinned(self.userName, isPinned)
157 | def verify(self):
158 | return self.core.add_friend(**self.verifyDict)
159 | def __deepcopy__(self, memo):
160 | r = super(User, self).__deepcopy__(memo)
161 | r.verifyDict = copy.deepcopy(self.verifyDict)
162 | return r
163 | def __setstate__(self, state):
164 | super(User, self).__setstate__(state)
165 | self.verifyDict = {}
166 | self['MemberList'] = fakeContactList
167 |
168 | class MassivePlatform(AbstractUserDict):
169 | def __init__(self, *args, **kwargs):
170 | super(MassivePlatform, self).__init__(*args, **kwargs)
171 | self.__setstate__(None)
172 | def __setstate__(self, state):
173 | super(MassivePlatform, self).__setstate__(state)
174 | self['MemberList'] = fakeContactList
175 |
176 | class Chatroom(AbstractUserDict):
177 | def __init__(self, *args, **kwargs):
178 | super(Chatroom, self).__init__(*args, **kwargs)
179 | memberList = ContactList()
180 | userName = self.get('UserName', '')
181 | refSelf = ref(self)
182 | def init_fn(parentList, d):
183 | d.chatroom = refSelf() or \
184 | parentList.core.search_chatrooms(userName=userName)
185 | memberList.set_default_value(init_fn, ChatroomMember)
186 | if 'MemberList' in self:
187 | for member in self.memberList:
188 | memberList.append(member)
189 | self['MemberList'] = memberList
190 | @property
191 | def core(self):
192 | return getattr(self, '_core', lambda: fakeItchat)() or fakeItchat
193 | @core.setter
194 | def core(self, value):
195 | self._core = ref(value)
196 | self.memberList.core = value
197 | for member in self.memberList:
198 | member.core = value
199 | def update(self, detailedMember=False):
200 | r = self.core.update_chatroom(self.userName, detailedMember)
201 | if r:
202 | update_info_dict(self, r)
203 | self['MemberList'] = r['MemberList']
204 | return r
205 | def set_alias(self, alias):
206 | return self.core.set_chatroom_name(self.userName, alias)
207 | def set_pinned(self, isPinned=True):
208 | return self.core.set_pinned(self.userName, isPinned)
209 | def delete_member(self, userName):
210 | return self.core.delete_member_from_chatroom(self.userName, userName)
211 | def add_member(self, userName):
212 | return self.core.add_member_into_chatroom(self.userName, userName)
213 | def search_member(self, name=None, userName=None, remarkName=None, nickName=None,
214 | wechatAccount=None):
215 | with self.core.storageClass.updateLock:
216 | if (name or userName or remarkName or nickName or wechatAccount) is None:
217 | return None
218 | elif userName: # return the only userName match
219 | for m in self.memberList:
220 | if m.userName == userName:
221 | return copy.deepcopy(m)
222 | else:
223 | matchDict = {
224 | 'RemarkName' : remarkName,
225 | 'NickName' : nickName,
226 | 'Alias' : wechatAccount, }
227 | for k in ('RemarkName', 'NickName', 'Alias'):
228 | if matchDict[k] is None:
229 | del matchDict[k]
230 | if name: # select based on name
231 | contact = []
232 | for m in self.memberList:
233 | if any([m.get(k) == name for k in ('RemarkName', 'NickName', 'Alias')]):
234 | contact.append(m)
235 | else:
236 | contact = self.memberList[:]
237 | if matchDict: # select again based on matchDict
238 | friendList = []
239 | for m in contact:
240 | if all([m.get(k) == v for k, v in matchDict.items()]):
241 | friendList.append(m)
242 | return copy.deepcopy(friendList)
243 | else:
244 | return copy.deepcopy(contact)
245 | def __setstate__(self, state):
246 | super(Chatroom, self).__setstate__(state)
247 | if not 'MemberList' in self:
248 | self['MemberList'] = fakeContactList
249 |
250 | class ChatroomMember(AbstractUserDict):
251 | def __init__(self, *args, **kwargs):
252 | super(AbstractUserDict, self).__init__(*args, **kwargs)
253 | self.__setstate__(None)
254 | @property
255 | def chatroom(self):
256 | r = getattr(self, '_chatroom', lambda: fakeChatroom)()
257 | if r is None:
258 | userName = getattr(self, '_chatroomUserName', '')
259 | r = self.core.search_chatrooms(userName=userName)
260 | if isinstance(r, dict):
261 | self.chatroom = r
262 | return r or fakeChatroom
263 | @chatroom.setter
264 | def chatroom(self, value):
265 | if isinstance(value, dict) and 'UserName' in value:
266 | self._chatroom = ref(value)
267 | self._chatroomUserName = value['UserName']
268 | def get_head_image(self, imageDir=None):
269 | return self.core.get_head_img(self.userName, self.chatroom.userName, picDir=imageDir)
270 | def delete_member(self, userName):
271 | return self.core.delete_member_from_chatroom(self.chatroom.userName, self.userName)
272 | def send_raw_msg(self, msgType, content):
273 | return ReturnValue({'BaseResponse': {
274 | 'Ret': -1006,
275 | 'ErrMsg': '%s can not send message directly' % \
276 | self.__class__.__name__, }, })
277 | def send_msg(self, msg='Test Message'):
278 | return ReturnValue({'BaseResponse': {
279 | 'Ret': -1006,
280 | 'ErrMsg': '%s can not send message directly' % \
281 | self.__class__.__name__, }, })
282 | def send_file(self, fileDir, mediaId=None):
283 | return ReturnValue({'BaseResponse': {
284 | 'Ret': -1006,
285 | 'ErrMsg': '%s can not send message directly' % \
286 | self.__class__.__name__, }, })
287 | def send_image(self, fileDir, mediaId=None):
288 | return ReturnValue({'BaseResponse': {
289 | 'Ret': -1006,
290 | 'ErrMsg': '%s can not send message directly' % \
291 | self.__class__.__name__, }, })
292 | def send_video(self, fileDir=None, mediaId=None):
293 | return ReturnValue({'BaseResponse': {
294 | 'Ret': -1006,
295 | 'ErrMsg': '%s can not send message directly' % \
296 | self.__class__.__name__, }, })
297 | def send(self, msg, mediaId=None):
298 | return ReturnValue({'BaseResponse': {
299 | 'Ret': -1006,
300 | 'ErrMsg': '%s can not send message directly' % \
301 | self.__class__.__name__, }, })
302 | def __setstate__(self, state):
303 | super(ChatroomMember, self).__setstate__(state)
304 | self['MemberList'] = fakeContactList
305 |
306 | def wrap_user_dict(d):
307 | userName = d.get('UserName')
308 | if '@@' in userName:
309 | r = Chatroom(d)
310 | elif d.get('VerifyFlag', 8) & 8 == 0:
311 | r = User(d)
312 | else:
313 | r = MassivePlatform(d)
314 | return r
315 |
316 | fakeItchat = UnInitializedItchat()
317 | fakeContactList = ContactList()
318 | fakeChatroom = Chatroom()
319 |
--------------------------------------------------------------------------------
/itchat/components/login.py:
--------------------------------------------------------------------------------
1 | import os, time, re, io
2 | import threading
3 | import json, xml.dom.minidom
4 | import random
5 | import traceback, logging
6 | try:
7 | from httplib import BadStatusLine
8 | except ImportError:
9 | from http.client import BadStatusLine
10 |
11 | import requests
12 | from pyqrcode import QRCode
13 |
14 | from .. import config, utils
15 | from ..returnvalues import ReturnValue
16 | from ..storage.templates import wrap_user_dict
17 | from .contact import update_local_chatrooms, update_local_friends
18 | from .messages import produce_msg
19 |
20 | logger = logging.getLogger('itchat')
21 |
22 | def load_login(core):
23 | core.login = login
24 | core.get_QRuuid = get_QRuuid
25 | core.get_QR = get_QR
26 | core.check_login = check_login
27 | core.web_init = web_init
28 | core.show_mobile_login = show_mobile_login
29 | core.start_receiving = start_receiving
30 | core.get_msg = get_msg
31 | core.logout = logout
32 |
33 | def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
34 | loginCallback=None, exitCallback=None):
35 | if self.alive or self.isLogging:
36 | logger.warning('itchat has already logged in.')
37 | return
38 | self.isLogging = True
39 | while self.isLogging:
40 | uuid = push_login(self)
41 | if uuid:
42 | qrStorage = io.BytesIO()
43 | else:
44 | logger.info('Getting uuid of QR code.')
45 | while not self.get_QRuuid():
46 | time.sleep(1)
47 | logger.info('Downloading QR code.')
48 | qrStorage = self.get_QR(enableCmdQR=enableCmdQR,
49 | picDir=picDir, qrCallback=qrCallback)
50 | logger.info('Please scan the QR code to log in.')
51 | isLoggedIn = False
52 | while not isLoggedIn:
53 | status = self.check_login()
54 | if hasattr(qrCallback, '__call__'):
55 | qrCallback(uuid=self.uuid, status=status, qrcode=qrStorage.getvalue())
56 | if status == '200':
57 | isLoggedIn = True
58 | elif status == '201':
59 | if isLoggedIn is not None:
60 | logger.info('Please press confirm on your phone.')
61 | isLoggedIn = None
62 | elif status != '408':
63 | break
64 | if isLoggedIn:
65 | break
66 | elif self.isLogging:
67 | logger.info('Log in time out, reloading QR code.')
68 | else:
69 | return # log in process is stopped by user
70 | logger.info('Loading the contact, this may take a little while.')
71 | self.web_init()
72 | self.show_mobile_login()
73 | self.get_contact(True)
74 | if hasattr(loginCallback, '__call__'):
75 | r = loginCallback()
76 | else:
77 | utils.clear_screen()
78 | if os.path.exists(picDir or config.DEFAULT_QR):
79 | os.remove(picDir or config.DEFAULT_QR)
80 | logger.info('Login successfully as %s' % self.storageClass.nickName)
81 | self.start_receiving(exitCallback)
82 | self.isLogging = False
83 |
84 | def push_login(core):
85 | cookiesDict = core.s.cookies.get_dict()
86 | if 'wxuin' in cookiesDict:
87 | url = '%s/cgi-bin/mmwebwx-bin/webwxpushloginurl?uin=%s' % (
88 | config.BASE_URL, cookiesDict['wxuin'])
89 | headers = { 'User-Agent' : config.USER_AGENT }
90 | r = core.s.get(url, headers=headers).json()
91 | if 'uuid' in r and r.get('ret') in (0, '0'):
92 | core.uuid = r['uuid']
93 | return r['uuid']
94 | return False
95 |
96 | def get_QRuuid(self):
97 | url = '%s/jslogin' % config.BASE_URL
98 | params = {
99 | 'appid' : 'wx782c26e4c19acffb',
100 | 'fun' : 'new', }
101 | headers = { 'User-Agent' : config.USER_AGENT }
102 | r = self.s.get(url, params=params, headers=headers)
103 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";'
104 | data = re.search(regx, r.text)
105 | if data and data.group(1) == '200':
106 | self.uuid = data.group(2)
107 | return self.uuid
108 |
109 | def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
110 | uuid = uuid or self.uuid
111 | picDir = picDir or config.DEFAULT_QR
112 | qrStorage = io.BytesIO()
113 | qrCode = QRCode('https://login.weixin.qq.com/l/' + uuid)
114 | qrCode.png(qrStorage, scale=10)
115 | if hasattr(qrCallback, '__call__'):
116 | qrCallback(uuid=uuid, status='0', qrcode=qrStorage.getvalue())
117 | else:
118 | with open(picDir, 'wb') as f:
119 | f.write(qrStorage.getvalue())
120 | if enableCmdQR:
121 | utils.print_cmd_qr(qrCode.text(1), enableCmdQR=enableCmdQR)
122 | else:
123 | utils.print_qr(picDir)
124 | return qrStorage
125 |
126 | def check_login(self, uuid=None):
127 | uuid = uuid or self.uuid
128 | url = '%s/cgi-bin/mmwebwx-bin/login' % config.BASE_URL
129 | localTime = int(time.time())
130 | params = 'loginicon=true&uuid=%s&tip=1&r=%s&_=%s' % (
131 | uuid, int(-localTime / 1579), localTime)
132 | headers = { 'User-Agent' : config.USER_AGENT }
133 | r = self.s.get(url, params=params, headers=headers)
134 | regx = r'window.code=(\d+)'
135 | data = re.search(regx, r.text)
136 | if data and data.group(1) == '200':
137 | if process_login_info(self, r.text):
138 | return '200'
139 | else:
140 | return '400'
141 | elif data:
142 | return data.group(1)
143 | else:
144 | return '400'
145 |
146 | def process_login_info(core, loginContent):
147 | ''' when finish login (scanning qrcode)
148 | * syncUrl and fileUploadingUrl will be fetched
149 | * deviceid and msgid will be generated
150 | * skey, wxsid, wxuin, pass_ticket will be fetched
151 | '''
152 | regx = r'window.redirect_uri="(\S+)";'
153 | core.loginInfo['url'] = re.search(regx, loginContent).group(1)
154 | headers = { 'User-Agent' : config.USER_AGENT }
155 | r = core.s.get(core.loginInfo['url'], headers=headers, allow_redirects=False)
156 | core.loginInfo['url'] = core.loginInfo['url'][:core.loginInfo['url'].rfind('/')]
157 | for indexUrl, detailedUrl in (
158 | ("wx2.qq.com" , ("file.wx2.qq.com", "webpush.wx2.qq.com")),
159 | ("wx8.qq.com" , ("file.wx8.qq.com", "webpush.wx8.qq.com")),
160 | ("qq.com" , ("file.wx.qq.com", "webpush.wx.qq.com")),
161 | ("web2.wechat.com" , ("file.web2.wechat.com", "webpush.web2.wechat.com")),
162 | ("wechat.com" , ("file.web.wechat.com", "webpush.web.wechat.com"))):
163 | fileUrl, syncUrl = ['https://%s/cgi-bin/mmwebwx-bin' % url for url in detailedUrl]
164 | if indexUrl in core.loginInfo['url']:
165 | core.loginInfo['fileUrl'], core.loginInfo['syncUrl'] = \
166 | fileUrl, syncUrl
167 | break
168 | else:
169 | core.loginInfo['fileUrl'] = core.loginInfo['syncUrl'] = core.loginInfo['url']
170 | core.loginInfo['deviceid'] = 'e' + repr(random.random())[2:17]
171 | core.loginInfo['logintime'] = int(time.time() * 1e3)
172 | core.loginInfo['BaseRequest'] = {}
173 | for node in xml.dom.minidom.parseString(r.text).documentElement.childNodes:
174 | if node.nodeName == 'skey':
175 | core.loginInfo['skey'] = core.loginInfo['BaseRequest']['Skey'] = node.childNodes[0].data
176 | elif node.nodeName == 'wxsid':
177 | core.loginInfo['wxsid'] = core.loginInfo['BaseRequest']['Sid'] = node.childNodes[0].data
178 | elif node.nodeName == 'wxuin':
179 | core.loginInfo['wxuin'] = core.loginInfo['BaseRequest']['Uin'] = node.childNodes[0].data
180 | elif node.nodeName == 'pass_ticket':
181 | core.loginInfo['pass_ticket'] = core.loginInfo['BaseRequest']['DeviceID'] = node.childNodes[0].data
182 | if not all([key in core.loginInfo for key in ('skey', 'wxsid', 'wxuin', 'pass_ticket')]):
183 | logger.error('Your wechat account may be LIMITED to log in WEB wechat, error info:\n%s' % r.text)
184 | core.isLogging = False
185 | return False
186 | return True
187 |
188 | def web_init(self):
189 | url = '%s/webwxinit' % self.loginInfo['url']
190 | params = {
191 | 'r': int(-time.time() / 1579),
192 | 'pass_ticket': self.loginInfo['pass_ticket'], }
193 | data = { 'BaseRequest': self.loginInfo['BaseRequest'], }
194 | headers = {
195 | 'ContentType': 'application/json; charset=UTF-8',
196 | 'User-Agent' : config.USER_AGENT, }
197 | r = self.s.post(url, params=params, data=json.dumps(data), headers=headers)
198 | dic = json.loads(r.content.decode('utf-8', 'replace'))
199 | # deal with login info
200 | utils.emoji_formatter(dic['User'], 'NickName')
201 | self.loginInfo['InviteStartCount'] = int(dic['InviteStartCount'])
202 | self.loginInfo['User'] = wrap_user_dict(utils.struct_friend_info(dic['User']))
203 | self.memberList.append(self.loginInfo['User'])
204 | self.loginInfo['SyncKey'] = dic['SyncKey']
205 | self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
206 | for item in dic['SyncKey']['List']])
207 | self.storageClass.userName = dic['User']['UserName']
208 | self.storageClass.nickName = dic['User']['NickName']
209 | # deal with contact list returned when init
210 | contactList = dic.get('ContactList', [])
211 | chatroomList, otherList = [], []
212 | for m in contactList:
213 | if m['Sex'] != 0:
214 | otherList.append(m)
215 | elif '@@' in m['UserName']:
216 | m['MemberList'] = [] # don't let dirty info pollute the list
217 | chatroomList.append(m)
218 | elif '@' in m['UserName']:
219 | # mp will be dealt in update_local_friends as well
220 | otherList.append(m)
221 | if chatroomList:
222 | update_local_chatrooms(self, chatroomList)
223 | if otherList:
224 | update_local_friends(self, otherList)
225 | return dic
226 |
227 | def show_mobile_login(self):
228 | url = '%s/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (
229 | self.loginInfo['url'], self.loginInfo['pass_ticket'])
230 | data = {
231 | 'BaseRequest' : self.loginInfo['BaseRequest'],
232 | 'Code' : 3,
233 | 'FromUserName' : self.storageClass.userName,
234 | 'ToUserName' : self.storageClass.userName,
235 | 'ClientMsgId' : int(time.time()), }
236 | headers = {
237 | 'ContentType': 'application/json; charset=UTF-8',
238 | 'User-Agent' : config.USER_AGENT, }
239 | r = self.s.post(url, data=json.dumps(data), headers=headers)
240 | return ReturnValue(rawResponse=r)
241 |
242 | def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
243 | self.alive = True
244 | def maintain_loop():
245 | retryCount = 0
246 | while self.alive:
247 | try:
248 | i = sync_check(self)
249 | if i is None:
250 | self.alive = False
251 | elif i == '0':
252 | pass
253 | else:
254 | msgList, contactList = self.get_msg()
255 | if msgList:
256 | msgList = produce_msg(self, msgList)
257 | for msg in msgList:
258 | self.msgList.put(msg)
259 | if contactList:
260 | chatroomList, otherList = [], []
261 | for contact in contactList:
262 | if '@@' in contact['UserName']:
263 | chatroomList.append(contact)
264 | else:
265 | otherList.append(contact)
266 | chatroomMsg = update_local_chatrooms(self, chatroomList)
267 | chatroomMsg['User'] = self.loginInfo['User']
268 | self.msgList.put(chatroomMsg)
269 | update_local_friends(self, otherList)
270 | retryCount = 0
271 | except requests.exceptions.ReadTimeout:
272 | pass
273 | except:
274 | retryCount += 1
275 | logger.error(traceback.format_exc())
276 | if self.receivingRetryCount < retryCount:
277 | self.alive = False
278 | else:
279 | time.sleep(1)
280 | self.logout()
281 | if hasattr(exitCallback, '__call__'):
282 | exitCallback()
283 | else:
284 | logger.info('LOG OUT!')
285 | if getReceivingFnOnly:
286 | return maintain_loop
287 | else:
288 | maintainThread = threading.Thread(target=maintain_loop)
289 | maintainThread.setDaemon(True)
290 | maintainThread.start()
291 |
292 | def sync_check(self):
293 | url = '%s/synccheck' % self.loginInfo.get('syncUrl', self.loginInfo['url'])
294 | params = {
295 | 'r' : int(time.time() * 1000),
296 | 'skey' : self.loginInfo['skey'],
297 | 'sid' : self.loginInfo['wxsid'],
298 | 'uin' : self.loginInfo['wxuin'],
299 | 'deviceid' : self.loginInfo['deviceid'],
300 | 'synckey' : self.loginInfo['synckey'],
301 | '_' : self.loginInfo['logintime'], }
302 | headers = { 'User-Agent' : config.USER_AGENT }
303 | self.loginInfo['logintime'] += 1
304 | try:
305 | r = self.s.get(url, params=params, headers=headers, timeout=config.TIMEOUT)
306 | except requests.exceptions.ConnectionError as e:
307 | try:
308 | if not isinstance(e.args[0].args[1], BadStatusLine):
309 | raise
310 | # will return a package with status '0 -'
311 | # and value like:
312 | # 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
313 | # seems like status of typing, but before I make further achievement code will remain like this
314 | return '2'
315 | except:
316 | raise
317 | r.raise_for_status()
318 | regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}'
319 | pm = re.search(regx, r.text)
320 | if pm is None or pm.group(1) != '0':
321 | logger.debug('Unexpected sync check result: %s' % r.text)
322 | return None
323 | return pm.group(2)
324 |
325 | def get_msg(self):
326 | url = '%s/webwxsync?sid=%s&skey=%s&pass_ticket=%s' % (
327 | self.loginInfo['url'], self.loginInfo['wxsid'],
328 | self.loginInfo['skey'],self.loginInfo['pass_ticket'])
329 | data = {
330 | 'BaseRequest' : self.loginInfo['BaseRequest'],
331 | 'SyncKey' : self.loginInfo['SyncKey'],
332 | 'rr' : ~int(time.time()), }
333 | headers = {
334 | 'ContentType': 'application/json; charset=UTF-8',
335 | 'User-Agent' : config.USER_AGENT }
336 | r = self.s.post(url, data=json.dumps(data), headers=headers, timeout=config.TIMEOUT)
337 | dic = json.loads(r.content.decode('utf-8', 'replace'))
338 | if dic['BaseResponse']['Ret'] != 0: return None, None
339 | self.loginInfo['SyncKey'] = dic['SyncKey']
340 | self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
341 | for item in dic['SyncCheckKey']['List']])
342 | return dic['AddMsgList'], dic['ModContactList']
343 |
344 | def logout(self):
345 | if self.alive:
346 | url = '%s/webwxlogout' % self.loginInfo['url']
347 | params = {
348 | 'redirect' : 1,
349 | 'type' : 1,
350 | 'skey' : self.loginInfo['skey'], }
351 | headers = { 'User-Agent' : config.USER_AGENT }
352 | self.s.get(url, params=params, headers=headers)
353 | self.alive = False
354 | self.isLogging = False
355 | self.s.cookies.clear()
356 | del self.chatroomList[:]
357 | del self.memberList[:]
358 | del self.mpList[:]
359 | return ReturnValue({'BaseResponse': {
360 | 'ErrMsg': 'logout successfully.',
361 | 'Ret': 0, }})
362 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # API 列表
2 |
3 | 目前API列表暂时没有翻译的版本:
4 |
5 | 最新的API列表可以见core.py。
6 |
7 | ```python
8 | def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
9 | loginCallback=None, exitCallback=None):
10 | ''' log in like web wechat does
11 | for log in
12 | - a QR code will be downloaded and opened
13 | - then scanning status is logged, it paused for you confirm
14 | - finally it logged in and show your nickName
15 | for options
16 | - enableCmdQR: show qrcode in command line
17 | - integers can be used to fit strange char length
18 | - picDir: place for storing qrcode
19 | - qrCallback: method that should accept uuid, status, qrcode
20 | - loginCallback: callback after successfully logged in
21 | - if not set, screen is cleared and qrcode is deleted
22 | - exitCallback: callback after logged out
23 | - it contains calling of logout
24 | for usage
25 | ..code::python
26 |
27 | import itchat
28 | itchat.login()
29 |
30 | it is defined in components/login.py
31 | and of course every single move in login can be called outside
32 | - you may scan source code to see how
33 | - and modified according to your own demond
34 | '''
35 | raise NotImplementedError()
36 | def get_QRuuid(self):
37 | ''' get uuid for qrcode
38 | uuid is the symbol of qrcode
39 | - for logging in, you need to get a uuid first
40 | - for downloading qrcode, you need to pass uuid to it
41 | - for checking login status, uuid is also required
42 | if uuid has timed out, just get another
43 | it is defined in components/login.py
44 | '''
45 | raise NotImplementedError()
46 | def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
47 | ''' download and show qrcode
48 | for options
49 | - uuid: if uuid is not set, latest uuid you fetched will be used
50 | - enableCmdQR: show qrcode in cmd
51 | - picDir: where to store qrcode
52 | - qrCallback: method that should accept uuid, status, qrcode
53 | it is defined in components/login.py
54 | '''
55 | raise NotImplementedError()
56 | def check_login(self, uuid=None):
57 | ''' check login status
58 | for options:
59 | - uuid: if uuid is not set, latest uuid you fetched will be used
60 | for return values:
61 | - a string will be returned
62 | - for meaning of return values
63 | - 200: log in successfully
64 | - 201: waiting for press confirm
65 | - 408: uuid timed out
66 | - 0 : unknown error
67 | for processing:
68 | - syncUrl and fileUrl is set
69 | - BaseRequest is set
70 | blocks until reaches any of above status
71 | it is defined in components/login.py
72 | '''
73 | raise NotImplementedError()
74 | def web_init(self):
75 | ''' get info necessary for initializing
76 | for processing:
77 | - own account info is set
78 | - inviteStartCount is set
79 | - syncKey is set
80 | - part of contact is fetched
81 | it is defined in components/login.py
82 | '''
83 | raise NotImplementedError()
84 | def show_mobile_login(self):
85 | ''' show web wechat login sign
86 | the sign is on the top of mobile phone wechat
87 | sign will be added after sometime even without calling this function
88 | it is defined in components/login.py
89 | '''
90 | raise NotImplementedError()
91 | def start_receiving(self, finishCallback=None):
92 | ''' open a thread for heart loop and receiving messages
93 | for options:
94 | - finishCallback: callback after logged out
95 | - it contains calling of logout
96 | for processing:
97 | - messages: msgs are formatted and passed on to registered fns
98 | - contact : chatrooms are updated when related info is received
99 | it is defined in components/login.py
100 | '''
101 | raise NotImplementedError()
102 | def get_msg(self):
103 | ''' fetch messages
104 | for fetching
105 | - method blocks for sometime until
106 | - new messages are to be received
107 | - or anytime they like
108 | - synckey is updated with returned synccheckkey
109 | it is defined in components/login.py
110 | '''
111 | raise NotImplementedError()
112 | def logout(self):
113 | ''' logout
114 | if core is now alive
115 | logout will tell wechat backstage to logout
116 | and core gets ready for another login
117 | it is defined in components/login.py
118 | '''
119 | raise NotImplementedError()
120 | def update_chatroom(self, userName, detailedMember=False):
121 | ''' update chatroom
122 | for chatroom contact
123 | - a chatroom contact need updating to be detailed
124 | - detailed means members, encryid, etc
125 | - auto updating of heart loop is a more detailed updating
126 | - member uin will also be filled
127 | - once called, updated info will be stored
128 | for options
129 | - userName: 'UserName' key of chatroom or a list of it
130 | - detailedMember: whether to get members of contact
131 | it is defined in components/contact.py
132 | '''
133 | raise NotImplementedError()
134 | def update_friend(self, userName):
135 | ''' update chatroom
136 | for friend contact
137 | - once called, updated info will be stored
138 | for options
139 | - userName: 'UserName' key of a friend or a list of it
140 | it is defined in components/contact.py
141 | '''
142 | raise NotImplementedError()
143 | def get_contact(self, update=False):
144 | ''' fetch part of contact
145 | for part
146 | - all the massive platforms and friends are fetched
147 | - if update, only starred chatrooms are fetched
148 | for options
149 | - update: if not set, local value will be returned
150 | for results
151 | - chatroomList will be returned
152 | it is defined in components/contact.py
153 | '''
154 | raise NotImplementedError()
155 | def get_friends(self, update=False):
156 | ''' fetch friends list
157 | for options
158 | - update: if not set, local value will be returned
159 | for results
160 | - a list of friends' info dicts will be returned
161 | it is defined in components/contact.py
162 | '''
163 | raise NotImplementedError()
164 | def get_chatrooms(self, update=False, contactOnly=False):
165 | ''' fetch chatrooms list
166 | for options
167 | - update: if not set, local value will be returned
168 | - contactOnly: if set, only starred chatrooms will be returned
169 | for results
170 | - a list of chatrooms' info dicts will be returned
171 | it is defined in components/contact.py
172 | '''
173 | raise NotImplementedError()
174 | def get_mps(self, update=False):
175 | ''' fetch massive platforms list
176 | for options
177 | - update: if not set, local value will be returned
178 | for results
179 | - a list of platforms' info dicts will be returned
180 | it is defined in components/contact.py
181 | '''
182 | raise NotImplementedError()
183 | def set_alias(self, userName, alias):
184 | ''' set alias for a friend
185 | for options
186 | - userName: 'UserName' key of info dict
187 | - alias: new alias
188 | it is defined in components/contact.py
189 | '''
190 | raise NotImplementedError()
191 | def set_pinned(self, userName, isPinned=True):
192 | ''' set pinned for a friend or a chatroom
193 | for options
194 | - userName: 'UserName' key of info dict
195 | - isPinned: whether to pin
196 | it is defined in components/contact.py
197 | '''
198 | raise NotImplementedError()
199 | def add_friend(self, userName, status=2, verifyContent='', autoUpdate=True):
200 | ''' add a friend or accept a friend
201 | for options
202 | - userName: 'UserName' for friend's info dict
203 | - status:
204 | - for adding status should be 2
205 | - for accepting status should be 3
206 | - ticket: greeting message
207 | - userInfo: friend's other info for adding into local storage
208 | it is defined in components/contact.py
209 | '''
210 | raise NotImplementedError()
211 | def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
212 | ''' place for docs
213 | for options
214 | - if you want to get chatroom header: only set chatroomUserName
215 | - if you want to get friend header: only set userName
216 | - if you want to get chatroom member header: set both
217 | it is defined in components/contact.py
218 | '''
219 | raise NotImplementedError()
220 | def create_chatroom(self, memberList, topic=''):
221 | ''' create a chatroom
222 | for creating
223 | - its calling frequency is strictly limited
224 | for options
225 | - memberList: list of member info dict
226 | - topic: topic of new chatroom
227 | it is defined in components/contact.py
228 | '''
229 | raise NotImplementedError()
230 | def set_chatroom_name(self, chatroomUserName, name):
231 | ''' set chatroom name
232 | for setting
233 | - it makes an updating of chatroom
234 | - which means detailed info will be returned in heart loop
235 | for options
236 | - chatroomUserName: 'UserName' key of chatroom info dict
237 | - name: new chatroom name
238 | it is defined in components/contact.py
239 | '''
240 | raise NotImplementedError()
241 | def delete_member_from_chatroom(self, chatroomUserName, memberList):
242 | ''' deletes members from chatroom
243 | for deleting
244 | - you can't delete yourself
245 | - if so, no one will be deleted
246 | - strict-limited frequency
247 | for options
248 | - chatroomUserName: 'UserName' key of chatroom info dict
249 | - memberList: list of members' info dict
250 | it is defined in components/contact.py
251 | '''
252 | raise NotImplementedError()
253 | def add_member_into_chatroom(self, chatroomUserName, memberList,
254 | useInvitation=False):
255 | ''' add members into chatroom
256 | for adding
257 | - you can't add yourself or member already in chatroom
258 | - if so, no one will be added
259 | - if member will over 40 after adding, invitation must be used
260 | - strict-limited frequency
261 | for options
262 | - chatroomUserName: 'UserName' key of chatroom info dict
263 | - memberList: list of members' info dict
264 | - useInvitation: if invitation is not required, set this to use
265 | it is defined in components/contact.py
266 | '''
267 | raise NotImplementedError()
268 | def send_raw_msg(self, msgType, content, toUserName):
269 | ''' many messages are sent in a common way
270 | for demo
271 | .. code:: python
272 |
273 | @itchat.msg_register(itchat.content.CARD)
274 | def reply(msg):
275 | itchat.send_raw_msg(msg['MsgType'], msg['Content'], msg['FromUserName'])
276 |
277 | there are some little tricks here, you may discover them yourself
278 | but remember they are tricks
279 | it is defined in components/messages.py
280 | '''
281 | raise NotImplementedError()
282 | def send_msg(self, msg='Test Message', toUserName=None):
283 | ''' send plain text message
284 | for options
285 | - msg: should be unicode if there's non-ascii words in msg
286 | - toUserName: 'UserName' key of friend dict
287 | it is defined in components/messages.py
288 | '''
289 | raise NotImplementedError()
290 | def upload_file(self, fileDir, isPicture=False, isVideo=False):
291 | ''' upload file to server and get mediaId
292 | for options
293 | - fileDir: dir for file ready for upload
294 | - isPicture: whether file is a picture
295 | - isVideo: whether file is a video
296 | for return values
297 | will return a ReturnValue
298 | if succeeded, mediaId is in r['MediaId']
299 | it is defined in components/messages.py
300 | '''
301 | raise NotImplementedError()
302 | def send_file(self, fileDir, toUserName=None, mediaId=None):
303 | ''' send attachment
304 | for options
305 | - fileDir: dir for file ready for upload
306 | - mediaId: mediaId for file.
307 | - if set, file will not be uploaded twice
308 | - toUserName: 'UserName' key of friend dict
309 | it is defined in components/messages.py
310 | '''
311 | raise NotImplementedError()
312 | def send_image(self, fileDir, toUserName=None, mediaId=None):
313 | ''' send image
314 | for options
315 | - fileDir: dir for file ready for upload
316 | - if it's a gif, name it like 'xx.gif'
317 | - mediaId: mediaId for file.
318 | - if set, file will not be uploaded twice
319 | - toUserName: 'UserName' key of friend dict
320 | it is defined in components/messages.py
321 | '''
322 | raise NotImplementedError()
323 | def send_video(self, fileDir=None, toUserName=None, mediaId=None):
324 | ''' send video
325 | for options
326 | - fileDir: dir for file ready for upload
327 | - if mediaId is set, it's unnecessary to set fileDir
328 | - mediaId: mediaId for file.
329 | - if set, file will not be uploaded twice
330 | - toUserName: 'UserName' key of friend dict
331 | it is defined in components/messages.py
332 | '''
333 | raise NotImplementedError()
334 | def send(self, msg, toUserName=None, mediaId=None):
335 | ''' wrapped function for all the sending functions
336 | for options
337 | - msg: message starts with different string indicates different type
338 | - list of type string: ['@fil@', '@img@', '@msg@', '@vid@']
339 | - they are for file, image, plain text, video
340 | - if none of them matches, it will be sent like plain text
341 | - toUserName: 'UserName' key of friend dict
342 | - mediaId: if set, uploading will not be repeated
343 | it is defined in components/messages.py
344 | '''
345 | raise NotImplementedError()
346 | def dump_login_status(self, fileDir=None):
347 | ''' dump login status to a specific file
348 | for option
349 | - fileDir: dir for dumping login status
350 | it is defined in components/hotreload.py
351 | '''
352 | raise NotImplementedError()
353 | def load_login_status(self, fileDir,
354 | loginCallback=None, exitCallback=None):
355 | ''' load login status from a specific file
356 | for option
357 | - fileDir: file for loading login status
358 | - loginCallback: callback after successfully logged in
359 | - if not set, screen is cleared and qrcode is deleted
360 | - exitCallback: callback after logged out
361 | - it contains calling of logout
362 | it is defined in components/hotreload.py
363 | '''
364 | raise NotImplementedError()
365 | def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
366 | enableCmdQR=False, picDir=None, qrCallback=None,
367 | loginCallback=None, exitCallback=None):
368 | ''' log in like web wechat does
369 | for log in
370 | - a QR code will be downloaded and opened
371 | - then scanning status is logged, it paused for you confirm
372 | - finally it logged in and show your nickName
373 | for options
374 | - hotReload: enable hot reload
375 | - statusStorageDir: dir for storing log in status
376 | - enableCmdQR: show qrcode in command line
377 | - integers can be used to fit strange char length
378 | - picDir: place for storing qrcode
379 | - loginCallback: callback after successfully logged in
380 | - if not set, screen is cleared and qrcode is deleted
381 | - exitCallback: callback after logged out
382 | - it contains calling of logout
383 | - qrCallback: method that should accept uuid, status, qrcode
384 | for usage
385 | ..code::python
386 |
387 | import itchat
388 | itchat.auto_login()
389 |
390 | it is defined in components/register.py
391 | and of course every single move in login can be called outside
392 | - you may scan source code to see how
393 | - and modified according to your own demond
394 | '''
395 | raise NotImplementedError()
396 | def configured_reply(self):
397 | ''' determine the type of message and reply if its method is defined
398 | however, I use a strange way to determine whether a msg is from massive platform
399 | I haven't found a better solution here
400 | The main problem I'm worrying about is the mismatching of new friends added on phone
401 | If you have any good idea, pleeeease report an issue. I will be more than grateful.
402 | '''
403 | raise NotImplementedError()
404 | def msg_register(self, msgType,
405 | isFriendChat=False, isGroupChat=False, isMpChat=False):
406 | ''' a decorator constructor
407 | return a specific decorator based on information given
408 | '''
409 | raise NotImplementedError()
410 | def run(self, debug=True):
411 | ''' start auto respond
412 | for option
413 | - debug: if set, debug info will be shown on screen
414 | it is defined in components/register.py
415 | '''
416 | raise NotImplementedError()
417 | def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
418 | wechatAccount=None):
419 | return self.storageClass.search_friends(name, userName, remarkName,
420 | nickName, wechatAccount)
421 | def search_chatrooms(self, name=None, userName=None):
422 | return self.storageClass.search_chatrooms(name, userName)
423 | def search_mps(self, name=None, userName=None):
424 | return self.storageClass.search_mps(name, userName)
425 | ```
426 |
--------------------------------------------------------------------------------
/itchat/core.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from . import storage
4 | from .components import load_components
5 |
6 | class Core(object):
7 | def __init__(self):
8 | ''' init is the only method defined in core.py
9 | alive is value showing whether core is running
10 | - you should call logout method to change it
11 | - after logout, a core object can login again
12 | storageClass only uses basic python types
13 | - so for advanced uses, inherit it yourself
14 | receivingRetryCount is for receiving loop retry
15 | - it's 5 now, but actually even 1 is enough
16 | - failing is failing
17 | '''
18 | self.alive, self.isLogging = False, False
19 | self.storageClass = storage.Storage(self)
20 | self.memberList = self.storageClass.memberList
21 | self.mpList = self.storageClass.mpList
22 | self.chatroomList = self.storageClass.chatroomList
23 | self.msgList = self.storageClass.msgList
24 | self.loginInfo = {}
25 | self.s = requests.Session()
26 | self.uuid = None
27 | self.functionDict = {'FriendChat': {}, 'GroupChat': {}, 'MpChat': {}}
28 | self.useHotReload, self.hotReloadDir = False, 'itchat.pkl'
29 | self.receivingRetryCount = 5
30 | def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
31 | loginCallback=None, exitCallback=None):
32 | ''' log in like web wechat does
33 | for log in
34 | - a QR code will be downloaded and opened
35 | - then scanning status is logged, it paused for you confirm
36 | - finally it logged in and show your nickName
37 | for options
38 | - enableCmdQR: show qrcode in command line
39 | - integers can be used to fit strange char length
40 | - picDir: place for storing qrcode
41 | - qrCallback: method that should accept uuid, status, qrcode
42 | - loginCallback: callback after successfully logged in
43 | - if not set, screen is cleared and qrcode is deleted
44 | - exitCallback: callback after logged out
45 | - it contains calling of logout
46 | for usage
47 | ..code::python
48 |
49 | import itchat
50 | itchat.login()
51 |
52 | it is defined in components/login.py
53 | and of course every single move in login can be called outside
54 | - you may scan source code to see how
55 | - and modified according to your own demand
56 | '''
57 | raise NotImplementedError()
58 | def get_QRuuid(self):
59 | ''' get uuid for qrcode
60 | uuid is the symbol of qrcode
61 | - for logging in, you need to get a uuid first
62 | - for downloading qrcode, you need to pass uuid to it
63 | - for checking login status, uuid is also required
64 | if uuid has timed out, just get another
65 | it is defined in components/login.py
66 | '''
67 | raise NotImplementedError()
68 | def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
69 | ''' download and show qrcode
70 | for options
71 | - uuid: if uuid is not set, latest uuid you fetched will be used
72 | - enableCmdQR: show qrcode in cmd
73 | - picDir: where to store qrcode
74 | - qrCallback: method that should accept uuid, status, qrcode
75 | it is defined in components/login.py
76 | '''
77 | raise NotImplementedError()
78 | def check_login(self, uuid=None):
79 | ''' check login status
80 | for options:
81 | - uuid: if uuid is not set, latest uuid you fetched will be used
82 | for return values:
83 | - a string will be returned
84 | - for meaning of return values
85 | - 200: log in successfully
86 | - 201: waiting for press confirm
87 | - 408: uuid timed out
88 | - 0 : unknown error
89 | for processing:
90 | - syncUrl and fileUrl is set
91 | - BaseRequest is set
92 | blocks until reaches any of above status
93 | it is defined in components/login.py
94 | '''
95 | raise NotImplementedError()
96 | def web_init(self):
97 | ''' get info necessary for initializing
98 | for processing:
99 | - own account info is set
100 | - inviteStartCount is set
101 | - syncKey is set
102 | - part of contact is fetched
103 | it is defined in components/login.py
104 | '''
105 | raise NotImplementedError()
106 | def show_mobile_login(self):
107 | ''' show web wechat login sign
108 | the sign is on the top of mobile phone wechat
109 | sign will be added after sometime even without calling this function
110 | it is defined in components/login.py
111 | '''
112 | raise NotImplementedError()
113 | def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
114 | ''' open a thread for heart loop and receiving messages
115 | for options:
116 | - exitCallback: callback after logged out
117 | - it contains calling of logout
118 | - getReceivingFnOnly: if True thread will not be created and started. Instead, receive fn will be returned.
119 | for processing:
120 | - messages: msgs are formatted and passed on to registered fns
121 | - contact : chatrooms are updated when related info is received
122 | it is defined in components/login.py
123 | '''
124 | raise NotImplementedError()
125 | def get_msg(self):
126 | ''' fetch messages
127 | for fetching
128 | - method blocks for sometime until
129 | - new messages are to be received
130 | - or anytime they like
131 | - synckey is updated with returned synccheckkey
132 | it is defined in components/login.py
133 | '''
134 | raise NotImplementedError()
135 | def logout(self):
136 | ''' logout
137 | if core is now alive
138 | logout will tell wechat backstage to logout
139 | and core gets ready for another login
140 | it is defined in components/login.py
141 | '''
142 | raise NotImplementedError()
143 | def update_chatroom(self, userName, detailedMember=False):
144 | ''' update chatroom
145 | for chatroom contact
146 | - a chatroom contact need updating to be detailed
147 | - detailed means members, encryid, etc
148 | - auto updating of heart loop is a more detailed updating
149 | - member uin will also be filled
150 | - once called, updated info will be stored
151 | for options
152 | - userName: 'UserName' key of chatroom or a list of it
153 | - detailedMember: whether to get members of contact
154 | it is defined in components/contact.py
155 | '''
156 | raise NotImplementedError()
157 | def update_friend(self, userName):
158 | ''' update chatroom
159 | for friend contact
160 | - once called, updated info will be stored
161 | for options
162 | - userName: 'UserName' key of a friend or a list of it
163 | it is defined in components/contact.py
164 | '''
165 | raise NotImplementedError()
166 | def get_contact(self, update=False):
167 | ''' fetch part of contact
168 | for part
169 | - all the massive platforms and friends are fetched
170 | - if update, only starred chatrooms are fetched
171 | for options
172 | - update: if not set, local value will be returned
173 | for results
174 | - chatroomList will be returned
175 | it is defined in components/contact.py
176 | '''
177 | raise NotImplementedError()
178 | def get_friends(self, update=False):
179 | ''' fetch friends list
180 | for options
181 | - update: if not set, local value will be returned
182 | for results
183 | - a list of friends' info dicts will be returned
184 | it is defined in components/contact.py
185 | '''
186 | raise NotImplementedError()
187 | def get_chatrooms(self, update=False, contactOnly=False):
188 | ''' fetch chatrooms list
189 | for options
190 | - update: if not set, local value will be returned
191 | - contactOnly: if set, only starred chatrooms will be returned
192 | for results
193 | - a list of chatrooms' info dicts will be returned
194 | it is defined in components/contact.py
195 | '''
196 | raise NotImplementedError()
197 | def get_mps(self, update=False):
198 | ''' fetch massive platforms list
199 | for options
200 | - update: if not set, local value will be returned
201 | for results
202 | - a list of platforms' info dicts will be returned
203 | it is defined in components/contact.py
204 | '''
205 | raise NotImplementedError()
206 | def set_alias(self, userName, alias):
207 | ''' set alias for a friend
208 | for options
209 | - userName: 'UserName' key of info dict
210 | - alias: new alias
211 | it is defined in components/contact.py
212 | '''
213 | raise NotImplementedError()
214 | def set_pinned(self, userName, isPinned=True):
215 | ''' set pinned for a friend or a chatroom
216 | for options
217 | - userName: 'UserName' key of info dict
218 | - isPinned: whether to pin
219 | it is defined in components/contact.py
220 | '''
221 | raise NotImplementedError()
222 | def add_friend(self, userName, status=2, verifyContent='', autoUpdate=True):
223 | ''' add a friend or accept a friend
224 | for options
225 | - userName: 'UserName' for friend's info dict
226 | - status:
227 | - for adding status should be 2
228 | - for accepting status should be 3
229 | - ticket: greeting message
230 | - userInfo: friend's other info for adding into local storage
231 | it is defined in components/contact.py
232 | '''
233 | raise NotImplementedError()
234 | def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
235 | ''' place for docs
236 | for options
237 | - if you want to get chatroom header: only set chatroomUserName
238 | - if you want to get friend header: only set userName
239 | - if you want to get chatroom member header: set both
240 | it is defined in components/contact.py
241 | '''
242 | raise NotImplementedError()
243 | def create_chatroom(self, memberList, topic=''):
244 | ''' create a chatroom
245 | for creating
246 | - its calling frequency is strictly limited
247 | for options
248 | - memberList: list of member info dict
249 | - topic: topic of new chatroom
250 | it is defined in components/contact.py
251 | '''
252 | raise NotImplementedError()
253 | def set_chatroom_name(self, chatroomUserName, name):
254 | ''' set chatroom name
255 | for setting
256 | - it makes an updating of chatroom
257 | - which means detailed info will be returned in heart loop
258 | for options
259 | - chatroomUserName: 'UserName' key of chatroom info dict
260 | - name: new chatroom name
261 | it is defined in components/contact.py
262 | '''
263 | raise NotImplementedError()
264 | def delete_member_from_chatroom(self, chatroomUserName, memberList):
265 | ''' deletes members from chatroom
266 | for deleting
267 | - you can't delete yourself
268 | - if so, no one will be deleted
269 | - strict-limited frequency
270 | for options
271 | - chatroomUserName: 'UserName' key of chatroom info dict
272 | - memberList: list of members' info dict
273 | it is defined in components/contact.py
274 | '''
275 | raise NotImplementedError()
276 | def add_member_into_chatroom(self, chatroomUserName, memberList,
277 | useInvitation=False):
278 | ''' add members into chatroom
279 | for adding
280 | - you can't add yourself or member already in chatroom
281 | - if so, no one will be added
282 | - if member will over 40 after adding, invitation must be used
283 | - strict-limited frequency
284 | for options
285 | - chatroomUserName: 'UserName' key of chatroom info dict
286 | - memberList: list of members' info dict
287 | - useInvitation: if invitation is not required, set this to use
288 | it is defined in components/contact.py
289 | '''
290 | raise NotImplementedError()
291 | def send_raw_msg(self, msgType, content, toUserName):
292 | ''' many messages are sent in a common way
293 | for demo
294 | .. code:: python
295 |
296 | @itchat.msg_register(itchat.content.CARD)
297 | def reply(msg):
298 | itchat.send_raw_msg(msg['MsgType'], msg['Content'], msg['FromUserName'])
299 |
300 | there are some little tricks here, you may discover them yourself
301 | but remember they are tricks
302 | it is defined in components/messages.py
303 | '''
304 | raise NotImplementedError()
305 | def send_msg(self, msg='Test Message', toUserName=None):
306 | ''' send plain text message
307 | for options
308 | - msg: should be unicode if there's non-ascii words in msg
309 | - toUserName: 'UserName' key of friend dict
310 | it is defined in components/messages.py
311 | '''
312 | raise NotImplementedError()
313 | def upload_file(self, fileDir, isPicture=False, isVideo=False,
314 | toUserName='filehelper', file_=None, preparedFile=None):
315 | ''' upload file to server and get mediaId
316 | for options
317 | - fileDir: dir for file ready for upload
318 | - isPicture: whether file is a picture
319 | - isVideo: whether file is a video
320 | for return values
321 | will return a ReturnValue
322 | if succeeded, mediaId is in r['MediaId']
323 | it is defined in components/messages.py
324 | '''
325 | raise NotImplementedError()
326 | def send_file(self, fileDir, toUserName=None, mediaId=None, file_=None):
327 | ''' send attachment
328 | for options
329 | - fileDir: dir for file ready for upload
330 | - mediaId: mediaId for file.
331 | - if set, file will not be uploaded twice
332 | - toUserName: 'UserName' key of friend dict
333 | it is defined in components/messages.py
334 | '''
335 | raise NotImplementedError()
336 | def send_image(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
337 | ''' send image
338 | for options
339 | - fileDir: dir for file ready for upload
340 | - if it's a gif, name it like 'xx.gif'
341 | - mediaId: mediaId for file.
342 | - if set, file will not be uploaded twice
343 | - toUserName: 'UserName' key of friend dict
344 | it is defined in components/messages.py
345 | '''
346 | raise NotImplementedError()
347 | def send_video(self, fileDir=None, toUserName=None, mediaId=None, file_=None):
348 | ''' send video
349 | for options
350 | - fileDir: dir for file ready for upload
351 | - if mediaId is set, it's unnecessary to set fileDir
352 | - mediaId: mediaId for file.
353 | - if set, file will not be uploaded twice
354 | - toUserName: 'UserName' key of friend dict
355 | it is defined in components/messages.py
356 | '''
357 | raise NotImplementedError()
358 | def send(self, msg, toUserName=None, mediaId=None):
359 | ''' wrapped function for all the sending functions
360 | for options
361 | - msg: message starts with different string indicates different type
362 | - list of type string: ['@fil@', '@img@', '@msg@', '@vid@']
363 | - they are for file, image, plain text, video
364 | - if none of them matches, it will be sent like plain text
365 | - toUserName: 'UserName' key of friend dict
366 | - mediaId: if set, uploading will not be repeated
367 | it is defined in components/messages.py
368 | '''
369 | raise NotImplementedError()
370 | def revoke(self, msgId, toUserName, localId=None):
371 | ''' revoke message with its and msgId
372 | for options
373 | - msgId: message Id on server
374 | - toUserName: 'UserName' key of friend dict
375 | - localId: message Id at local (optional)
376 | it is defined in components/messages.py
377 | '''
378 | raise NotImplementedError()
379 | def dump_login_status(self, fileDir=None):
380 | ''' dump login status to a specific file
381 | for option
382 | - fileDir: dir for dumping login status
383 | it is defined in components/hotreload.py
384 | '''
385 | raise NotImplementedError()
386 | def load_login_status(self, fileDir,
387 | loginCallback=None, exitCallback=None):
388 | ''' load login status from a specific file
389 | for option
390 | - fileDir: file for loading login status
391 | - loginCallback: callback after successfully logged in
392 | - if not set, screen is cleared and qrcode is deleted
393 | - exitCallback: callback after logged out
394 | - it contains calling of logout
395 | it is defined in components/hotreload.py
396 | '''
397 | raise NotImplementedError()
398 | def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
399 | enableCmdQR=False, picDir=None, qrCallback=None,
400 | loginCallback=None, exitCallback=None):
401 | ''' log in like web wechat does
402 | for log in
403 | - a QR code will be downloaded and opened
404 | - then scanning status is logged, it paused for you confirm
405 | - finally it logged in and show your nickName
406 | for options
407 | - hotReload: enable hot reload
408 | - statusStorageDir: dir for storing log in status
409 | - enableCmdQR: show qrcode in command line
410 | - integers can be used to fit strange char length
411 | - picDir: place for storing qrcode
412 | - loginCallback: callback after successfully logged in
413 | - if not set, screen is cleared and qrcode is deleted
414 | - exitCallback: callback after logged out
415 | - it contains calling of logout
416 | - qrCallback: method that should accept uuid, status, qrcode
417 | for usage
418 | ..code::python
419 |
420 | import itchat
421 | itchat.auto_login()
422 |
423 | it is defined in components/register.py
424 | and of course every single move in login can be called outside
425 | - you may scan source code to see how
426 | - and modified according to your own demond
427 | '''
428 | raise NotImplementedError()
429 | def configured_reply(self):
430 | ''' determine the type of message and reply if its method is defined
431 | however, I use a strange way to determine whether a msg is from massive platform
432 | I haven't found a better solution here
433 | The main problem I'm worrying about is the mismatching of new friends added on phone
434 | If you have any good idea, pleeeease report an issue. I will be more than grateful.
435 | '''
436 | raise NotImplementedError()
437 | def msg_register(self, msgType,
438 | isFriendChat=False, isGroupChat=False, isMpChat=False):
439 | ''' a decorator constructor
440 | return a specific decorator based on information given
441 | '''
442 | raise NotImplementedError()
443 | def run(self, debug=True, blockThread=True):
444 | ''' start auto respond
445 | for option
446 | - debug: if set, debug info will be shown on screen
447 | it is defined in components/register.py
448 | '''
449 | raise NotImplementedError()
450 | def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
451 | wechatAccount=None):
452 | return self.storageClass.search_friends(name, userName, remarkName,
453 | nickName, wechatAccount)
454 | def search_chatrooms(self, name=None, userName=None):
455 | return self.storageClass.search_chatrooms(name, userName)
456 | def search_mps(self, name=None, userName=None):
457 | return self.storageClass.search_mps(name, userName)
458 |
459 | load_components(Core)
460 |
--------------------------------------------------------------------------------