├── 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
│ ├── index.md
│ └── messages.md
├── FAQ.md
├── index.md
├── tutorial
│ ├── tutorial2.md
│ ├── tutorial0.md
│ └── tutorial1.md
└── api.md
├── .gitignore
├── itchat
├── content.py
├── config.py
├── components
│ ├── __init__.py
│ ├── hotreload.py
│ ├── register.py
│ ├── login.py
│ ├── messages.py
│ └── contact.py
├── log.py
├── returnvalues.py
├── __init__.py
├── storage.py
└── utils.py
├── mkdocs.yml
├── LICENSE.md
├── 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/Rockyzsu/ItChat/master/docs/bootstrap/img/favicon.ico
--------------------------------------------------------------------------------
/docs/bootstrap/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rockyzsu/ItChat/master/docs/bootstrap/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/docs/bootstrap/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rockyzsu/ItChat/master/docs/bootstrap/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/docs/bootstrap/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rockyzsu/ItChat/master/docs/bootstrap/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | test/*
4 | itchat.egg-info/*
5 | *.pyc
6 | *.swp
7 | test.py
8 | itchat.pkl
9 | QR.jpg
10 | .DS_Store
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/itchat/content.py:
--------------------------------------------------------------------------------
1 | TEXT = 'Text'
2 | MAP = 'Map'
3 | CARD = 'Card'
4 | NOTE = 'Note'
5 | SHARING = 'Sharing'
6 | PICTURE = 'Picture'
7 | RECORDING = 'Recording'
8 | ATTACHMENT = 'Attachment'
9 | VIDEO = 'Video'
10 | FRIENDS = 'Friends'
11 | SYSTEM = 'System'
12 |
--------------------------------------------------------------------------------
/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.2.18'
4 | BASE_URL = 'https://login.weixin.qq.com'
5 | OS = platform.system() #Windows, Linux, Darwin
6 | DIR = os.getcwd()
7 | DEFAULT_QR = 'QR.jpg'
8 |
9 | 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'
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/bootstrap/toc.html:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/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: 测试用机器人能稳定在线多个月。如果你在测试过程中发现无法稳定登陆,请检查**登陆手机**及主机是否稳定连接网络。
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 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | **The MIT License (MIT)**
2 |
3 | Copyright (c) 2016 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/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 | https://github.com/littlecodersh/ItChat/tree/robot
5 | """
6 |
7 | from setuptools import setup, find_packages
8 | from codecs import open
9 | from os import path
10 | import itchat
11 |
12 | here = path.abspath(path.dirname(__file__))
13 |
14 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
15 | long_description = f.read()
16 |
17 | setup(
18 | name='itchat',
19 |
20 | version=itchat.__version__,
21 |
22 | description='A complete wechat personal account api',
23 | long_description=long_description,
24 |
25 | url='https://github.com/littlecodersh/ItChat',
26 |
27 | author='LittleCoder',
28 | author_email='i7meavnktqegm1b@qq.com',
29 |
30 | license='MIT',
31 |
32 | classifiers=[
33 | 'Development Status :: 3 - Alpha',
34 |
35 | 'Intended Audience :: Developers',
36 | 'Topic :: Software Development :: Libraries :: Python Modules',
37 |
38 | 'License :: OSI Approved :: MIT License',
39 |
40 | 'Programming Language :: Python :: 2',
41 | 'Programming Language :: Python :: 2.6',
42 | 'Programming Language :: Python :: 2.7',
43 | ],
44 |
45 | keywords='wechat itchat api robot weixin personal extend',
46 |
47 | # You can just specify the packages manually here if your project is
48 | # simple. Or you can use find_packages().
49 | packages=find_packages(),
50 |
51 | install_requires=['requests'],
52 |
53 | # List additional groups of dependencies here
54 | extras_require={},
55 | )
56 |
--------------------------------------------------------------------------------
/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['Content']
14 |
15 | itchat.auto_login()
16 | itchat.run()
17 | ```
18 |
19 | ## 常用消息的配置
20 |
21 | itchat支持所有的消息类型与群聊,下面的示例中演示了对于这些消息类型简单的配置。
22 |
23 | ```python
24 | #coding=utf8
25 | import itchat
26 | from itchat.content import *
27 |
28 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
29 | def text_reply(msg):
30 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
31 |
32 | # 以下四类的消息的Text键下存放了用于下载消息内容的方法,传入文件地址即可
33 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
34 | def download_files(msg):
35 | msg['Text'](msg['FileName'])
36 | return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
37 |
38 | # 收到好友邀请自动添加好友
39 | @itchat.msg_register(FRIENDS)
40 | def add_friend(msg):
41 | itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
42 | itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
43 |
44 | # 在注册时增加isGroupChat=True将判定为群聊回复
45 | @itchat.msg_register(TEXT, isGroupChat = True)
46 | def groupchat_reply(msg):
47 | if msg['isAt']:
48 | itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
49 |
50 | itchat.auto_login(True)
51 | itchat.run()
52 | ```
53 |
54 | 当然这里不需要深究为什么这些东西可以这么写,我在这里放出了示例程序只是为了给你一个该sdk相关代码大概样子的概念。
55 |
56 | 有了大概的模式的了解之后我们就可以进入下一部分的介绍。
57 |
--------------------------------------------------------------------------------
/itchat/returnvalues.py:
--------------------------------------------------------------------------------
1 | #coding=utf8
2 | import sys
3 |
4 | TRANSLATE = 'Chinese'
5 |
6 | class ReturnValue(dict):
7 | def __init__(self, returnValueDict={}, rawResponse=None):
8 | if rawResponse:
9 | try:
10 | returnValueDict = rawResponse.json()
11 | except ValueError:
12 | returnValueDict = {
13 | 'BaseResponse': {
14 | 'Ret': -1004,
15 | 'ErrMsg': 'Unexpected return value', },
16 | 'Data': rawResponse.content, }
17 | for k, v in returnValueDict.items(): self[k] = v
18 | if not 'BaseResponse' in self:
19 | self['BaseResponse'] = {
20 | 'ErrMsg': 'no BaseResponse in raw response',
21 | 'Ret': -1000, }
22 | if TRANSLATE:
23 | self['BaseResponse']['RawMsg'] = self['BaseResponse'].get('ErrMsg', '')
24 | self['BaseResponse']['ErrMsg'] = \
25 | TRANSLATION[TRANSLATE].get(
26 | self['BaseResponse'].get('Ret', '')) \
27 | or self['BaseResponse'].get('ErrMsg', u'No ErrMsg')
28 | self['BaseResponse']['RawMsg'] = \
29 | self['BaseResponse']['RawMsg'] or self['BaseResponse']['ErrMsg']
30 | def __nonzero__(self):
31 | return self['BaseResponse'].get('Ret') == 0
32 | def __bool__(self):
33 | return self.__nonzero__()
34 | def __str__(self):
35 | return '{%s}' % ', '.join(
36 | ['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
37 | def __repr__(self):
38 | return '' % self.__str__()
39 |
40 | TRANSLATION = {
41 | 'Chinese': {
42 | -1000: u'返回值不带BaseResponse',
43 | -1001: u'无法找到对应的成员',
44 | -1002: u'文件位置错误',
45 | -1003: u'服务器拒绝连接',
46 | -1004: u'服务器返回异常值',
47 | -1005: u'参数错误',
48 | 0: u'请求成功',
49 | },
50 | }
51 |
--------------------------------------------------------------------------------
/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 | ```python
8 | send(msg='Text Message', toUserName=None)
9 | ```
10 | * 所需值:
11 | * msg:消息内容
12 | * '@fil@文件地址'将会被识别为传送文件,'@img@图片地址'将会被识别为传送图片,'@vid@视频地址'将会被识别为小视频
13 | * toUserName:发送对象,如果留空将会发送给自己
14 | * 返回值:发送成功->True, 失败->False
15 | * 程序示例:使用的素材可以在[这里][attachment]下载
16 |
17 | ```python
18 | #coding=utf8
19 | import itchat
20 |
21 | itchat.auto_login()
22 | itchat.send('Hello world!')
23 | # 请确保该程序目录下存在:gz.gif以及xlsx.xlsx
24 | itchat.send('@img@%s' % 'gz.gif')
25 | itchat.send('@fil@%s' % 'xlsx.xlsx')
26 | itchat.send('@vid@%s' % 'demo.mp4')
27 | ```
28 |
29 | ## send_msg方法
30 | * 方法:
31 | ```python
32 | send_msg(msg='Text Message', toUserName=None)
33 | ```
34 | * 所需值:
35 | * msg:消息内容
36 | * toUserName:发送对象,如果留空将会发送给自己
37 | * 返回值:发送成功->True, 失败->False
38 | * 程序示例:
39 |
40 | ```python
41 | import itchat
42 |
43 | itchat.auto_login()
44 | itchat.send_msg('Hello world')
45 | ```
46 |
47 | ## send_file方法
48 | * 方法:
49 | ```python
50 | send_file(fileDir, toUserName=None)
51 | ```
52 | * 所需值:
53 | * fileDir:文件路径(不存在该文件时将打印无此文件的提醒)
54 | * toUserName:发送对象,如果留空将会发送给自己
55 | * 返回值:发送成功->True, 失败->False
56 | * 程序示例:使用的素材可以在[这里][attachment](提取码:eaee)下载
57 |
58 | ```python
59 | #coding=utf8
60 | import itchat
61 |
62 | itchat.auto_login()
63 | # 请确保该程序目录下存在:xlsx.xlsx
64 | itchat.send_file('xlsx.xlsx')
65 | ```
66 |
67 | ## send_img方法
68 | * 方法:
69 | ```python
70 | send_img(fileDir, toUserName=None)
71 | ```
72 | * 所需值:
73 | * fileDir:文件路径(不存在该文件时将打印无此文件的提醒)
74 | * toUserName:发送对象,如果留空将会发送给自己
75 | * 返回值:发送成功->True, 失败->False
76 | * 程序示例:使用的素材可以在[这里][attachment](提取码:eaee)下载
77 |
78 | ```python
79 | #coding=utf8
80 | import itchat
81 |
82 | itchat.auto_login()
83 | # 请确保该程序目录下存在:gz.gif
84 | itchat.send_img('gz.gif')
85 | ```
86 |
87 | ## send_video方法
88 | * 方法:
89 | ```python
90 | send_video(fileDir, toUserName=None)
91 | ```
92 | * 所需值:
93 | * fileDir:文件路径(不存在该文件时将打印无此文件的提醒)
94 | * toUserName:发送对象,如果留空将会发送给自己
95 | * 返回值:发送成功->True, 失败->False
96 | * 需要保证发送的视频为一个实质的mp4文件
97 |
98 | ```python
99 | #coding=utf8
100 | import itchat
101 |
102 | itchat.auto_login()
103 | # 请确保该程序目录下存在:demo.mp4
104 | itchat.send_file('demo.mp4')
105 | ```
106 |
107 | [attachment]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/%E4%B8%8A%E4%BC%A0%E7%B4%A0%E6%9D%90.zip
108 |
--------------------------------------------------------------------------------
/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 | # components.hotreload
55 | dump_login_status = originInstance.dump_login_status
56 | load_login_status = originInstance.load_login_status
57 | # components.register
58 | auto_login = originInstance.auto_login
59 | configured_reply = originInstance.configured_reply
60 | msg_register = originInstance.msg_register
61 | run = originInstance.run
62 | # other functions
63 | search_friends = originInstance.search_friends
64 | search_chatrooms = originInstance.search_chatrooms
65 | search_mps = originInstance.search_mps
66 | set_logging = set_logging
67 |
--------------------------------------------------------------------------------
/itchat/components/hotreload.py:
--------------------------------------------------------------------------------
1 | import pickle, os
2 | import logging, traceback
3 |
4 | import requests
5 |
6 | from ..config import VERSION
7 | from ..returnvalues import ReturnValue
8 | from .contact import update_local_chatrooms
9 | from .messages import produce_msg
10 |
11 | logger = logging.getLogger('itchat')
12 |
13 | def load_hotreload(core):
14 | core.dump_login_status = dump_login_status
15 | core.load_login_status = load_login_status
16 |
17 | def dump_login_status(self, fileDir=None):
18 | fileDir = fileDir or self.hotReloadDir
19 | try:
20 | with open(fileDir, 'w') as f:
21 | f.write('itchat - DELETE THIS')
22 | os.remove(fileDir)
23 | except:
24 | raise Exception('Incorrect fileDir')
25 | status = {
26 | 'version' : VERSION,
27 | 'loginInfo' : self.loginInfo,
28 | 'cookies' : self.s.cookies.get_dict(),
29 | 'storage' : self.storageClass.dumps()}
30 | with open(fileDir, 'wb') as f:
31 | pickle.dump(status, f)
32 | logger.debug('Dump login status for hot reload successfully.')
33 |
34 | def load_login_status(self, fileDir,
35 | loginCallback=None, exitCallback=None):
36 | try:
37 | with open(fileDir, 'rb') as f:
38 | j = pickle.load(f)
39 | except Exception as e:
40 | logger.debug('No such file, loading login status failed.')
41 | return ReturnValue({'BaseResponse': {
42 | 'ErrMsg': 'No such file, loading login status failed.',
43 | 'Ret': -1002, }})
44 |
45 | if j.get('version', '') != VERSION:
46 | logger.debug(('you have updated itchat from %s to %s, ' +
47 | 'so cached status is ignored') % (
48 | j.get('version', 'old version'), VERSION))
49 | return ReturnValue({'BaseResponse': {
50 | 'ErrMsg': 'cached status ignored because of version',
51 | 'Ret': -1005, }})
52 | self.loginInfo = j['loginInfo']
53 | self.s.cookies = requests.utils.cookiejar_from_dict(j['cookies'])
54 | self.storageClass.loads(j['storage'])
55 | msgList, contactList = self.get_msg()
56 | if (msgList or contactList) is None:
57 | self.logout()
58 | logger.debug('server refused, loading login status failed.')
59 | return ReturnValue({'BaseResponse': {
60 | 'ErrMsg': 'server refused, loading login status failed.',
61 | 'Ret': -1003, }})
62 | else:
63 | if contactList:
64 | for contact in contactList:
65 | if '@@' in contact['UserName']:
66 | update_local_chatrooms(self, [contact])
67 | else:
68 | update_local_chatrooms(self, [contact])
69 | if msgList:
70 | msgList = produce_msg(self, msgList)
71 | for msg in msgList: self.msgList.put(msg)
72 | self.start_receiving(exitCallback)
73 | logger.debug('loading login status succeeded.')
74 | if hasattr(loginCallback, '__call__'):
75 | loginCallback()
76 | return ReturnValue({'BaseResponse': {
77 | 'ErrMsg': 'loading login status succeeded.',
78 | 'Ret': 0, }})
79 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/itchat/storage.py:
--------------------------------------------------------------------------------
1 | import os, time, copy
2 | try:
3 | import Queue
4 | except ImportError:
5 | import queue as Queue
6 |
7 | class Storage(object):
8 | def __init__(self):
9 | self.userName = None
10 | self.nickName = None
11 | self.memberList = []
12 | self.mpList = []
13 | self.chatroomList = []
14 | self.msgList = Queue.Queue(-1)
15 | self.lastInputUserName = None
16 | def dumps(self):
17 | return {
18 | 'userName' : self.userName,
19 | 'nickName' : self.nickName,
20 | 'memberList' : self.memberList,
21 | 'mpList' : self.mpList,
22 | 'chatroomList' : self.chatroomList,
23 | 'lastInputUserName' : self.lastInputUserName, }
24 | def loads(self, j):
25 | self.userName = j.get('userName', None)
26 | self.nickName = j.get('nickName', None)
27 | del self.memberList[:]
28 | for i in j.get('memberList', []): self.memberList.append(i)
29 | del self.mpList[:]
30 | for i in j.get('mpList', []): self.mpList.append(i)
31 | del self.chatroomList[:]
32 | for i in j.get('chatroomList', []): self.chatroomList.append(i)
33 | self.lastInputUserName = j.get('lastInputUserName', None)
34 | def search_friends(self, name=None, userName=None, remarkName=None, nickName=None,
35 | wechatAccount=None):
36 | if (name or userName or remarkName or nickName or wechatAccount) is None:
37 | return copy.deepcopy(self.memberList[0]) # my own account
38 | elif userName: # return the only userName match
39 | for m in self.memberList:
40 | if m['UserName'] == userName: return copy.deepcopy(m)
41 | else:
42 | matchDict = {
43 | 'RemarkName' : remarkName,
44 | 'NickName' : nickName,
45 | 'Alias' : wechatAccount, }
46 | for k in ('RemarkName', 'NickName', 'Alias'):
47 | if matchDict[k] is None: del matchDict[k]
48 | if name: # select based on name
49 | contract = []
50 | for m in self.memberList:
51 | if any([m.get(k) == name for k in ('RemarkName', 'NickName', 'Alias')]):
52 | contract.append(m)
53 | else:
54 | contract = self.memberList[:]
55 | if matchDict: # select again based on matchDict
56 | friendList = []
57 | for m in contract:
58 | if all([m.get(k) == v for k, v in matchDict.items()]):
59 | friendList.append(m)
60 | return copy.deepcopy(friendList)
61 | else:
62 | return copy.deepcopy(contract)
63 | def search_chatrooms(self, name=None, userName=None):
64 | if userName is not None:
65 | for m in self.chatroomList:
66 | if m['UserName'] == userName: return copy.deepcopy(m)
67 | elif name is not None:
68 | matchList = []
69 | for m in self.chatroomList:
70 | if name in m['NickName']: matchList.append(copy.deepcopy(m))
71 | return matchList
72 | def search_mps(self, name=None, userName=None):
73 | if userName is not None:
74 | for m in self.mpList:
75 | if m['UserName'] == userName: return copy.deepcopy(m)
76 | elif name is not None:
77 | matchList = []
78 | for m in self.mpList:
79 | if name in m['NickName']: matchList.append(copy.deepcopy(m))
80 | return matchList
81 |
--------------------------------------------------------------------------------
/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 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
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 | * isAt: 判断是否@本号
60 | * ActualNickName: 实际NickName
61 | * Content: 实际Content
62 |
63 | 可以通过本程序测试:
64 |
65 | ```python
66 | import itchat
67 | from itchat.content import TEXT
68 |
69 | @itchat.msg_register(TEXT, isGroupChat = True)
70 | def text_reply(msg):
71 | print(msg['isAt'])
72 | print(msg['ActualNickName'])
73 | print(msg['Content'])
74 |
75 | itchat.auto_login()
76 | itchat.run()
77 | ```
78 |
79 | ## 注册消息的优先级
80 |
81 | 优先级分别为:后注册消息先于先注册消息,带参数消息先于不带参数消息。
82 |
83 | 以下面的两个程序为例:
84 |
85 | ```python
86 | import itchat
87 | from itchat.content import *
88 |
89 | itchat.auto_login()
90 |
91 | @itchat.msg_register(TEXT)
92 | def text_reply(msg):
93 | return 'This is the old register'
94 |
95 | @itchat.msg_register(TEXT)
96 | def text_reply(msg):
97 | return 'This is a new one'
98 |
99 | itchat.run()
100 | ```
101 |
102 | 在私聊发送文本时将会回复`This is a new one`。
103 | ```python
104 | import itchat
105 | from itchat.content import *
106 |
107 | itchat.auto_login()
108 |
109 | @itchat.msg_register
110 | def general_reply(msg):
111 | return 'I received a %s' % msg['Type']
112 |
113 | @itchat.msg_register(TEXT)
114 | def text_reply(msg):
115 | return 'You said to me one to one: %s' % msg['Text']
116 |
117 | itchat.run()
118 | ```
119 |
120 | 仅在私聊发送文本时将会回复`You said to me one to one`,其余情况将会回复`I received a ...`。
121 |
122 | ## 动态注册消息
123 |
124 | 动态注册时可以选择将`itchat.run()`放入另一线程或使用`configured_reply()`方法处理消息。
125 |
126 | 两种方法分别是:
127 |
128 | ```python
129 | # 使用另一线程,但注意不要让程序运行终止
130 | import thread
131 |
132 | thread.start_new_thread(itchat.run, ())
133 |
134 | # 使用configured_reply方法
135 | while 1:
136 | itchat.configured_reply()
137 | # some other functions
138 | time.sleep(1)
139 | ```
140 |
141 | 以下给出一个动态注册的例子:
142 |
143 | ```python
144 | #coding=utf8
145 | import thread
146 |
147 | import itchat
148 | from itchat.content import *
149 |
150 | replyToGroupChat = True
151 | functionStatus = False
152 |
153 | def change_function():
154 | if replyToGroupChat != functionStatus:
155 | if replyToGroupChat:
156 | @itchat.msg_register(TEXT, isGroupChat = True)
157 | def group_text_reply(msg):
158 | if u'关闭' in msg['Text']:
159 | replyToGroupChat = False
160 | return u'已关闭'
161 | elif u'开启' in msg['Text']:
162 | return u'已经在运行'
163 | return u'输入"关闭"或者"开启"测试功能'
164 | else:
165 | @itchat.msg_register(TEXT, isGroupChat = True)
166 | def group_text_reply(msg):
167 | if u'开启' in msg['Text']:
168 | replyToGroupChat = True
169 | return u'重新开启成功'
170 | functionStatus = replyToGroupChat
171 |
172 | thread.start_new_thread(itchat.run, ())
173 |
174 | while 1:
175 | change_function()
176 | time.sleep(.1)
177 | ```
178 |
--------------------------------------------------------------------------------
/docs/intro/login.md:
--------------------------------------------------------------------------------
1 | # 登陆
2 |
3 | 在上一章中你看到了基本的注册与登陆,而显然登陆使用的是itchat提供了`auto_login`方法,调用即可完成登录。
4 |
5 | 一般而言,我们都会在完成消息的注册后登陆。
6 |
7 | 当然这里需要特别强调的是三点,分别是短时间关闭重连、命令行二维码与自定义登陆内容。
8 | * itchat提供了登陆状态暂存,关闭程序后一定时间内不需要扫码即可登录。
9 | * 为了方便在无图形界面使用itchat,程序内置了命令行二维码的显示。
10 | * 如果你需要就登录状态就一些修改(例如更改提示语、二维码出现后邮件发送等)。
11 |
12 | ## 短时间关闭程序后重连
13 |
14 | 这样即使程序关闭,一定时间内重新开启也可以不用重新扫码。
15 |
16 | 最简单的用法就是给`auto_login`方法传入值为真的hotReload。
17 |
18 | 该方法会生成一个静态文件`itchat.pkl`,用于存储登陆的状态。
19 |
20 | ```python
21 | import itchat
22 | from itchat.content import TEXT
23 |
24 | @itchat.msg_register(TEXT)
25 | def simple_reply(msg):
26 | print(msg['Text'])
27 |
28 | itchat.auto_login(hotReload=True)
29 | itchat.run()
30 | itchat.dump_login_status()
31 | ```
32 |
33 | 通过设置statusStorageDir可以将静态文件指定为其他的值。
34 |
35 | 这一内置选项其实就相当于使用了以下两个函数的这一段程序:
36 |
37 | ```python
38 | import itchat
39 | from itchat.content import TEXT
40 |
41 | if itchat.load_login_status():
42 | @itchat.msg_register(TEXT)
43 | def simple_reply(msg):
44 | print(msg['Text'])
45 | itchat.run()
46 | itchat.dump_login_status()
47 | else:
48 | itchat.auto_login()
49 | itchat.dump_login_status()
50 | print('Config stored, so exit.')
51 | ```
52 |
53 | 其中load_login_status与dump_login_status分别对应读取与导出设置。
54 |
55 | 通过设置传入的fileDir的值可以设定导入导出的文件。
56 |
57 | ## 命令行二维码显示
58 |
59 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
60 |
61 | ```python
62 | itchat.auto_login(enableCmdQR=True)
63 | ```
64 |
65 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
66 |
67 | ```python
68 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
69 | itchat.auto_login(enableCmdQR=2)
70 | ```
71 |
72 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
73 |
74 | ```python
75 | itchat.auto_login(enableCmdQR=-1)
76 | ```
77 |
78 | ## 自定义登录过程
79 |
80 | 如果需要控制登录的过程,可以阅读下面的内容。
81 |
82 | 同时itchat也提供了登陆所需的每一步的方法,登陆的过程按顺序为:
83 | * 获取二维码uuid
84 | * 获取二维码
85 | * 判断是否已经登陆成功
86 | * 获取初始化数据
87 | * 更新微信相关信息(通讯录、手机登陆状态)
88 | * 循环扫描新信息(开启心跳)
89 |
90 | ### 获取二维码uuid
91 |
92 | 获取生成二维码所需的uuid,并返回。
93 |
94 | * 方法名称:`get_QRuuid`
95 | * 所需值:无
96 | * 返回值:成功->uuid,失败->None
97 |
98 | ### 获取二维码
99 |
100 | 根据uuid获取二维码并打开,返回是否成功。
101 |
102 | * 方法名称:`get_QR`
103 | * 所需值:uuid
104 | * 返回值:成功->True,失败->False
105 |
106 | ### 判断是否已经登陆成功
107 |
108 | 判断是否已经登陆成功,返回扫描的状态码。
109 |
110 | * 方法名称:`check_login`
111 | * 所需值:uuid
112 | * 返回值:登陆成功->'200',已扫描二维码->'201',二维码失效->'408',未获取到信息->'0'
113 |
114 | ### 获取初始化数据
115 |
116 | 获取微信用户信息以及心跳所需要的数据。
117 |
118 | * 方法名称:`web_init`
119 | * 所需值:无
120 | * 返回值:存储登录微信用户信息的字典
121 |
122 | ### 获取微信通讯录
123 |
124 | 获取微信的所有好友信息并更新。
125 |
126 | * 方法名称:`get_friends`(曾用名:`get_contract`)
127 | * 所需值:无
128 | * 返回值:存储好友信息的列表
129 |
130 | ### 更新微信手机登陆状态
131 |
132 | 在手机上显示登录状态。
133 |
134 | * 方法名称:`show_mobile_login`
135 | * 所需值:无
136 | * 返回值:无
137 |
138 | ### 循环扫描新信息(开启心跳)
139 |
140 | 循环扫描是否有新的消息,开启心跳包。
141 |
142 | * 方法名称:`start_receiving`
143 | * 所需值:无
144 | * 返回值:无
145 |
146 | ## 示例
147 |
148 | itchat自带的`auto_login`通过如下代码可以实现:
149 |
150 | ```python
151 | import itchat, time, sys
152 |
153 | def output_info(msg):
154 | print('[INFO] %s' % msg)
155 |
156 | def open_QR():
157 | for get_count in range(10):
158 | output_info('Getting uuid')
159 | uuid = itchat.get_QRuuid()
160 | while uuid is None: uuid = itchat.get_QRuuid();time.sleep(1)
161 | output_info('Getting QR Code')
162 | if itchat.get_QR(uuid): break
163 | elif get_count >= 9:
164 | output_info('Failed to get QR Code, please restart the program')
165 | sys.exit()
166 | output_info('Please scan the QR Code')
167 | return uuid
168 |
169 | uuid = open_QR()
170 | waitForConfirm = False
171 | while 1:
172 | status = itchat.check_login(uuid)
173 | if status == '200':
174 | break
175 | elif status == '201':
176 | if waitForConfirm:
177 | output_info('Please press confirm')
178 | waitForConfirm = True
179 | elif status == '408':
180 | output_info('Reloading QR Code')
181 | uuid = open_QR()
182 | waitForConfirm = False
183 | userInfo = itchat.web_init()
184 | itchat.show_mobile_login()
185 | itchat.get_friends(True)
186 | output_info('Login successfully as %s'%userInfo['NickName'])
187 | itchat.start_receiving()
188 |
189 | # Start auto-replying
190 | @itchat.msg_register
191 | def simple_reply(msg):
192 | if msg['Type'] == 'Text':
193 | return 'I received: %s' % msg['Content']
194 | itchat.run()
195 | ```
196 |
--------------------------------------------------------------------------------
/docs/intro/contact.md:
--------------------------------------------------------------------------------
1 | 在使用个人微信的过程当中主要有三种账号需要获取,分别为:
2 | * 好友
3 | * 公众号
4 | * 群聊
5 |
6 | itchat为这三种账号都提供了整体获取方法与搜索方法。
7 |
8 | 而群聊多出获取用户列表方法以及创建群聊、增加、删除用户的方法。
9 |
10 | 这里我们分这三种分别介绍如何使用。
11 |
12 | 在本章的最后还介绍了如何通过Uin唯一的确定好友与群聊。
13 |
14 | ## 好友
15 |
16 | 好友的获取方法为`get_friends`,将会返回完整的好友列表。
17 | * 其中每个好友为一个字典
18 | * 列表的第一项为本人的账号信息
19 | * 传入update键为True将可以更新好友列表并返回
20 |
21 | 好友的搜索方法为`search_friends`,有四种搜索方式:
22 | 1. 仅获取自己的用户信息
23 | 2. 获取特定`UserName`的用户信息
24 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
25 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
26 |
27 | 其中三、四项可以一同使用,下面是示例程序:
28 |
29 | ```python
30 | # 获取自己的用户信息,返回自己的属性字典
31 | itchat.search_friends()
32 | # 获取特定UserName的用户信息
33 | itchat.search_friends(userName='@abcdefg1234567')
34 | # 获取任何一项等于name键值的用户
35 | itchat.search_friends(name='littlecodersh')
36 | # 获取分别对应相应键值的用户
37 | itchat.search_friends(wechatAccount='littlecodersh')
38 | # 三、四项功能可以一同使用
39 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
40 | ```
41 |
42 | 更新用户信息的方法为`update_friend`。
43 | * 该方法需要传入用户的`UserName`,返回指定用户的最新信息
44 | * 同样也可以传入`UserName`组成的列表,那么相应的也会返回指定用户的最新信息组成的列表
45 |
46 | ```python
47 | memberList = itchat.update_friend('@abcdefg1234567')
48 | ```
49 |
50 | ## 公众号
51 |
52 | 公众号的获取方法为`get_mps`,将会返回完整的公众号列表。
53 | * 其中每个公众号为一个字典
54 | * 传入update键为True将可以更新公众号列表并返回
55 |
56 | 公众号的搜索方法为`search_mps`,有两种搜索方法:
57 | 1. 获取特定`UserName`的公众号
58 | 2. 获取名字中含有特定字符的公众号
59 |
60 | 如果两项都做了特定,将会仅返回特定`UserName`的公众号,下面是示例程序:
61 |
62 | ```python
63 | # 获取特定UserName的公众号,返回值为一个字典
64 | itchat.search_mps(userName='@abcdefg1234567')
65 | # 获取名字中含有特定字符的公众号,返回值为一个字典的列表
66 | itchat.search_mps(name='LittleCoder')
67 | # 以下方法相当于仅特定了UserName
68 | itchat.search_mps(userName='@abcdefg1234567', name='LittleCoder')
69 | ```
70 |
71 | ## 群聊
72 |
73 | 群聊的获取方法为`get_chatrooms`,将会返回完整的群聊列表。
74 | * 其中每个群聊为一个字典
75 | * 传入update键为True将可以更新群聊列表并返回通讯录中保存的群聊列表
76 | * 群聊列表为后台自动更新,如果中途意外退出存在极小的概率产生本地群聊消息与后台不同步
77 | * 为了保证群聊信息在热启动中可以被正确的加载,即使不需要持续在线的程序也需要运行`itchat.run()`
78 | * 如果不想要运行上述命令,请在退出程序前调用`itchat.dump_login_status()`,更新热拔插需要的信息
79 |
80 | 群聊的搜索方法为`search_chatrooms`,有两种搜索方法:
81 | 1. 获取特定`UserName`的群聊
82 | 2. 获取名字中含有特定字符的群聊
83 |
84 | 如果两项都做了特定,将会仅返回特定`UserName`的群聊,下面是示例程序:
85 |
86 | ```python
87 | # 获取特定UserName的群聊,返回值为一个字典
88 | itchat.search_chatrooms(userName='@@abcdefg1234567')
89 | # 获取名字中含有特定字符的群聊,返回值为一个字典的列表
90 | itchat.search_chatrooms(name='LittleCoder')
91 | # 以下方法相当于仅特定了UserName
92 | itchat.search_chatrooms(userName='@@abcdefg1234567', name='LittleCoder')
93 | ```
94 |
95 | 群聊用户列表的获取方法为`update_chatroom`。
96 | * 同样,如果想要更新该群聊的其他信息也可以用该方法
97 | * 群聊在首次获取中不会获取群聊的用户列表,所以需要调用该命令才能获取群聊的成员
98 | * 该方法需要传入群聊的`UserName`,返回特定群聊的详细信息
99 | * 同样也可以传入`UserName`组成的列表,那么相应的也会返回指定用户的最新信息组成的列表
100 |
101 | ```python
102 | memberList = itchat.update_chatroom('@@abcdefg1234567', detailedMember=True)
103 | ```
104 |
105 | 创建群聊、增加、删除群聊用户的方法如下所示:
106 | * 由于之前通过群聊检测是否被好友拉黑的程序,目前这三个方法都被严格限制了使用频率
107 | * 删除群聊需要本账号为群管理员,否则会失败
108 | * 将用户加入群聊有直接加入与发送邀请,通过`useInvitation`设置
109 | * 超过40人的群聊无法使用直接加入的加入方式,特别注意
110 |
111 | ```python
112 | memberList = itchat.get_friends()[1:]
113 | # 创建群聊,topic键值为群聊名
114 | chatroomUserName = itchat.create_chatroom(memberList, 'test chatroom')
115 | # 删除群聊内的用户
116 | itchat.delete_member_from_chatroom(chatroomUserName, memberList[0])
117 | # 增加用户进入群聊
118 | itchat.add_member_into_chatroom(chatroomUserName, memberList[0], useInvitation=False)
119 | ```
120 |
121 | ## Uins
122 |
123 | Uin 就是微信中用于标识用户的方式,每一个用户、群聊都有唯一且不同的Uin。
124 |
125 | 那么通过Uin,即使退出了重新登录,也可以轻松的确认正在对话的是上一次登陆的哪一个用户。
126 |
127 | 但注意,Uin与其他值不同,微信后台做了一定的限制,必须通过特殊的操作才能获取。
128 |
129 | 最简单来说,首次点开登陆用的手机端的某个好友或者群聊,itchat就能获取到该好友或者群聊的Uin。
130 |
131 | 如果想要通过程序获取,也可以用程序将某个好友或者群聊置顶(取消置顶)。
132 |
133 | 这里提供一个提示群聊更新的程序:
134 |
135 | ```python
136 | import re, sys, json
137 |
138 | import itchat
139 | from itchat.content import *
140 |
141 | itchat.auto_login(True)
142 |
143 | @itchat.msg_register(SYSTEM)
144 | def get_uin(msg):
145 | if msg['SystemInfo'] != 'uins': return
146 | ins = itchat.instanceList[0]
147 | fullContact = ins.memberList + ins.chatroomList + ins.mpList
148 | print('** Uin Updated **')
149 | for username in msg['Text']:
150 | member = itchat.utils.search_dict_list(
151 | fullContact, 'UserName', username)
152 | print(('%s: %s' % (
153 | member.get('NickName', ''), member['Uin']))
154 | .encode(sys.stdin.encoding, 'replace'))
155 |
156 | itchat.run(True)
157 | ```
158 |
159 | 每当Uin更新了,就会打印相应的更新情况。
160 |
161 | 同样的,如果你想要获取Uin更新的情况也通过获取SYSTEM类型消息实现。
162 |
--------------------------------------------------------------------------------
/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 |
10 | logger = logging.getLogger('itchat')
11 |
12 | def load_register(core):
13 | core.auto_login = auto_login
14 | core.configured_reply = configured_reply
15 | core.msg_register = msg_register
16 | core.run = run
17 |
18 | def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl',
19 | enableCmdQR=False, picDir=None, qrCallback=None,
20 | loginCallback=None, exitCallback=None):
21 | if not test_connect():
22 | logger.info("You don't have access to internet or wechat domain, so exit.")
23 | sys.exit()
24 | self.useHotReload = hotReload
25 | if hotReload:
26 | if self.load_login_status(statusStorageDir,
27 | loginCallback=loginCallback, exitCallback=exitCallback):
28 | return
29 | self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
30 | loginCallback=loginCallback, exitCallback=exitCallback)
31 | self.dump_login_status(statusStorageDir)
32 | self.hotReloadDir = statusStorageDir
33 | else:
34 | self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback,
35 | loginCallback=loginCallback, exitCallback=exitCallback)
36 |
37 | def configured_reply(self):
38 | ''' determine the type of message and reply if its method is defined
39 | however, I use a strange way to determine whether a msg is from massive platform
40 | I haven't found a better solution here
41 | The main problem I'm worrying about is the mismatching of new friends added on phone
42 | If you have any good idea, pleeeease report an issue. I will be more than grateful.
43 | '''
44 | try:
45 | msg = self.msgList.get(timeout=1)
46 | except Queue.Empty:
47 | pass
48 | else:
49 | if msg['FromUserName'] == self.storageClass.userName:
50 | actualOpposite = msg['ToUserName']
51 | else:
52 | actualOpposite = msg['FromUserName']
53 | if '@@' in actualOpposite:
54 | replyFn = self.functionDict['GroupChat'].get(msg['Type'])
55 | elif self.search_mps(userName=msg['FromUserName']):
56 | replyFn = self.functionDict['MpChat'].get(msg['Type'])
57 | elif '@' in actualOpposite or \
58 | actualOpposite in ('filehelper', 'fmessage'):
59 | replyFn = self.functionDict['FriendChat'].get(msg['Type'])
60 | else:
61 | replyFn = self.functionDict['MpChat'].get(msg['Type'])
62 | if replyFn is None:
63 | r = None
64 | else:
65 | try:
66 | r = replyFn(msg)
67 | if r is not None: self.send(r, msg.get('FromUserName'))
68 | except:
69 | logger.warning('An error occurred in registered function, use `itchat.run(debug=True)` to show detailed information')
70 | logger.debug(traceback.format_exc())
71 |
72 | def msg_register(self, msgType, isFriendChat=False, isGroupChat=False, isMpChat=False):
73 | ''' a decorator constructor
74 | return a specific decorator based on information given '''
75 | if not isinstance(msgType, list): msgType = [msgType]
76 | def _msg_register(fn):
77 | for _msgType in msgType:
78 | if isFriendChat:
79 | self.functionDict['FriendChat'][_msgType] = fn
80 | if isGroupChat:
81 | self.functionDict['GroupChat'][_msgType] = fn
82 | if isMpChat:
83 | self.functionDict['MpChat'][_msgType] = fn
84 | if not any((isFriendChat, isGroupChat, isMpChat)):
85 | self.functionDict['FriendChat'][_msgType] = fn
86 | return _msg_register
87 |
88 | def run(self, debug=False, blockThread=True):
89 | logger.info('Start auto replying.')
90 | if debug:
91 | set_logging(loggingLevel=logging.DEBUG)
92 | def reply_fn():
93 | try:
94 | while self.alive: self.configured_reply()
95 | except KeyboardInterrupt:
96 | if self.useHotReload: self.dump_login_status()
97 | self.alive = False
98 | logger.debug('itchat received an ^C and exit.')
99 | print('Bye~')
100 | if blockThread:
101 | reply_fn()
102 | else:
103 | replyThread = threading.Thread(target=reply_fn)
104 | replyThread.setDaemon(True)
105 | replyThread.start()
106 |
--------------------------------------------------------------------------------
/itchat/utils.py:
--------------------------------------------------------------------------------
1 | import re, os, sys, subprocess, copy
2 |
3 | try:
4 | from HTMLParser import HTMLParser
5 | except ImportError:
6 | from html.parser import HTMLParser
7 |
8 | import requests
9 |
10 | from . import config
11 |
12 | emojiRegex = re.compile(r'')
13 | htmlParser = HTMLParser()
14 | try:
15 | b = u'\u2588'
16 | sys.stdout.write(b + '\r')
17 | sys.stdout.flush()
18 | except UnicodeEncodeError:
19 | BLOCK = 'MM'
20 | else:
21 | BLOCK = b
22 | friendInfoTemplate = {}
23 | for k in ('UserName', 'City', 'DisplayName', 'PYQuanPin', 'RemarkPYInitial', 'Province',
24 | 'KeyWord', 'RemarkName', 'PYInitial', 'EncryChatRoomId', 'Alias', 'Signature',
25 | 'NickName', 'RemarkPYQuanPin', 'HeadImgUrl'): friendInfoTemplate[k] = ''
26 | for k in ('UniFriend', 'Sex', 'AppAccountFlag', 'VerifyFlag', 'ChatRoomId', 'HideInputBarFlag',
27 | 'AttrStatus', 'SnsFlag', 'MemberCount', 'OwnerUin', 'ContactFlag', 'Uin',
28 | 'StarFriend', 'Statues'): friendInfoTemplate[k] = 0
29 | friendInfoTemplate['MemberList'] = []
30 |
31 | def clear_screen():
32 | os.system('cls' if config.OS == 'Windows' else 'clear')
33 | def emoji_formatter(d, k):
34 | # _emoji_deebugger is for bugs about emoji match caused by wechat backstage
35 | # like :face with tears of joy: will be replaced with :cat face with tears of joy:
36 | def _emoji_debugger(d, k):
37 | s = d[k].replace('') # fix missing bug
39 | def __fix_miss_match(m):
40 | return '' % ({
41 | '1f63c': '1f601', '1f639': '1f602', '1f63a': '1f603',
42 | '1f4ab': '1f616', '1f64d': '1f614', '1f63b': '1f60d',
43 | '1f63d': '1f618', '1f64e': '1f621', '1f63f': '1f622',
44 | }.get(m.group(1), m.group(1)))
45 | return emojiRegex.sub(__fix_miss_match, s)
46 | def _emoji_formatter(m):
47 | s = m.group(1)
48 | if len(s) == 6:
49 | return ('\\U%s\\U%s'%(s[:2].rjust(8, '0'), s[2:].rjust(8, '0'))
50 | ).encode('utf8').decode('unicode-escape', 'replace')
51 | elif len(s) == 10:
52 | return ('\\U%s\\U%s'%(s[:5].rjust(8, '0'), s[5:].rjust(8, '0'))
53 | ).encode('utf8').decode('unicode-escape', 'replace')
54 | else:
55 | return ('\\U%s'%m.group(1).rjust(8, '0')
56 | ).encode('utf8').decode('unicode-escape', 'replace')
57 | d[k] = _emoji_debugger(d, k)
58 | d[k] = emojiRegex.sub(_emoji_formatter, d[k])
59 | def msg_formatter(d, k):
60 | emoji_formatter(d, k)
61 | d[k] = d[k].replace('
', '\n')
62 | d[k] = htmlParser.unescape(d[k])
63 | def check_file(fileDir):
64 | try:
65 | with open(fileDir): pass
66 | return True
67 | except:
68 | return False
69 | def print_qr(fileDir):
70 | if config.OS == 'Darwin':
71 | subprocess.call(['open', fileDir])
72 | elif config.OS == 'Linux':
73 | subprocess.call(['xdg-open', fileDir])
74 | else:
75 | os.startfile(fileDir)
76 | try:
77 | from PIL import Image
78 | def print_cmd_qr(fileDir, size = 37, padding = 3,
79 | white = BLOCK, black = ' ', enableCmdQR = True):
80 | img = Image.open(fileDir)
81 | times = img.size[0] / (size + padding * 2)
82 | rgb = img.convert('RGB')
83 | try:
84 | blockCount = int(enableCmdQR)
85 | assert(0 < abs(blockCount))
86 | except:
87 | blockCount = 1
88 | finally:
89 | white *= abs(blockCount)
90 | if blockCount < 0: white, black = black, white
91 | sys.stdout.write(' '*50 + '\r')
92 | sys.stdout.flush()
93 | qr = white * (size + 2) + '\n'
94 | startPoint = padding + 0.5
95 | for y in range(size):
96 | qr += white
97 | for x in range(size):
98 | r,g,b = rgb.getpixel(((x + startPoint) * times, (y + startPoint) * times))
99 | qr += white if r > 127 else black
100 | qr += white + '\n'
101 | qr += white * (size + 2) + '\n'
102 | sys.stdout.write(qr)
103 | except ImportError:
104 | def print_cmd_qr(fileDir, size = 37, padding = 3,
105 | white = BLOCK, black = ' ', enableCmdQR = True):
106 | print('pillow should be installed to use command line QRCode: pip install pillow')
107 | print_qr(fileDir)
108 | def struct_friend_info(knownInfo):
109 | member = copy.deepcopy(friendInfoTemplate)
110 | for k, v in copy.deepcopy(knownInfo).items(): member[k] = v
111 | return member
112 |
113 | def search_dict_list(l, key, value):
114 | ''' Search a list of dict
115 | * return dict with specific value & key '''
116 | for i in l:
117 | if i.get(key) == value: return i
118 |
119 | def print_line(msg, oneLine = False):
120 | if oneLine:
121 | sys.stdout.write(' '*40 + '\r')
122 | sys.stdout.flush()
123 | else:
124 | sys.stdout.write('\n')
125 | sys.stdout.write(msg.encode(sys.stdin.encoding or 'utf8', 'replace'
126 | ).decode(sys.stdin.encoding or 'utf8', 'replace'))
127 | sys.stdout.flush()
128 |
129 | def test_connect(retryTime=5):
130 | for i in range(retryTime):
131 | try:
132 | r = requests.get(config.BASE_URL)
133 | except:
134 | if i == retryTime-1: return False
135 | return True
136 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # itchat
2 |
3 | [![Gitter][gitter-picture]][gitter] ![py27][py27] ![py35][py35]
4 |
5 | itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。
6 |
7 | 使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。
8 |
9 | 当然,该api的使用远不止一个机器人,更多的功能等着你来发现,比如[这些][tutorial2]。
10 |
11 | 如今微信已经成为了个人社交的很大一部分,希望这个项目能够帮助你扩展你的个人的微信号、方便自己的生活。
12 |
13 | ## 安装
14 |
15 | 可以通过本命令安装itchat:
16 |
17 | ```python
18 | pip install itchat
19 | ```
20 |
21 | ## 简单入门实例
22 |
23 | 有了itchat,如果你想要给文件传输助手发一条信息,只需要这样:
24 |
25 | ```python
26 | import itchat
27 |
28 | itchat.auto_login()
29 |
30 | itchat.send('Hello, filehelper', toUserName='filehelper')
31 | ```
32 |
33 | 如果你想要回复发给自己的文本消息,只需要这样:
34 |
35 | ```python
36 | import itchat
37 |
38 | @itchat.msg_register(itchat.content.TEXT)
39 | def text_reply(msg):
40 | return msg['Text']
41 |
42 | itchat.auto_login()
43 | itchat.run()
44 | ```
45 |
46 | 一些进阶应用可以在下面的开源机器人的源码和进阶应用中看到,或者你也可以阅览[文档][document]。
47 |
48 | ## 试一试
49 |
50 | 这是一个基于这一项目的[开源小机器人][robot-source-code],百闻不如一见,有兴趣可以尝试一下。
51 |
52 | ![QRCode][robot-qr]
53 |
54 | ## 截屏
55 |
56 | ![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
57 |
58 | ## 进阶应用
59 |
60 | ### 各类型消息的注册
61 |
62 | 通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
63 |
64 | ```python
65 | #coding=utf8
66 | import itchat, time
67 | from itchat.content import *
68 |
69 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
70 | def text_reply(msg):
71 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
72 |
73 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
74 | def download_files(msg):
75 | msg['Text'](msg['FileName'])
76 | return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
77 |
78 | @itchat.msg_register(FRIENDS)
79 | def add_friend(msg):
80 | itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
81 | itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
82 |
83 | @itchat.msg_register(TEXT, isGroupChat=True)
84 | def text_reply(msg):
85 | if msg['isAt']:
86 | itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
87 |
88 | itchat.auto_login(True)
89 | itchat.run()
90 | ```
91 |
92 | ### 命令行二维码
93 |
94 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
95 |
96 | ```python
97 | itchat.auto_login(enableCmdQR=True)
98 | ```
99 |
100 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
101 |
102 | ```python
103 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
104 | itchat.auto_login(enableCmdQR=2)
105 | ```
106 |
107 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
108 |
109 | ```python
110 | itchat.auto_login(enableCmdQR=-1)
111 | ```
112 |
113 | ### 退出程序后暂存登陆状态
114 |
115 | 通过如下命令登陆,即使程序关闭,一定时间内重新开启也可以不用重新扫码。
116 |
117 | ```python
118 | itchat.auto_login(hotReload=True)
119 | ```
120 |
121 | ### 用户搜索
122 |
123 | 使用`search_friends`方法可以搜索用户,有四种搜索方式:
124 | 1. 仅获取自己的用户信息
125 | 2. 获取特定`UserName`的用户信息
126 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
127 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
128 |
129 | 其中三、四项可以一同使用,下面是示例程序:
130 |
131 | ```python
132 | # 获取自己的用户信息,返回自己的属性字典
133 | itchat.search_friends()
134 | # 获取特定UserName的用户信息
135 | itchat.search_friends(userName='@abcdefg1234567')
136 | # 获取任何一项等于name键值的用户
137 | itchat.search_friends(name='littlecodersh')
138 | # 获取分别对应相应键值的用户
139 | itchat.search_friends(wechatAccount='littlecodersh')
140 | # 三、四项功能可以一同使用
141 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
142 | ```
143 |
144 | 关于公众号、群聊的获取与搜索在文档中有更加详细的介绍。
145 |
146 | ### 附件的下载与发送
147 |
148 | itchat的附件下载方法存储在msg的Text键中。
149 |
150 | 发送的文件的文件名(图片给出的默认文件名)都存储在msg的FileName键中。
151 |
152 | 下载方法接受一个可用的位置参数(包括文件名),并将文件相应的存储。
153 |
154 | ```python
155 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
156 | def download_files(msg):
157 | msg['Text'](msg['FileName'])
158 | itchat.send('@%s@%s'%('img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']), msg['FromUserName'])
159 | return '%s received'%msg['Type']
160 | ```
161 |
162 | 如果你不需要下载到本地,仅想要读取二进制串进行进一步处理可以不传入参数,方法将会返回图片的二进制串。
163 |
164 | ```python
165 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
166 | def download_files(msg):
167 | with open(msg['FileName'], 'wb') as f:
168 | f.write(msg['Text']())
169 | ```
170 |
171 | ### 用户多开
172 |
173 | 使用如下命令可以完成多开的操作:
174 |
175 | ```python
176 | import itchat
177 |
178 | newInstance = itchat.new_instance()
179 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
180 |
181 | @newInstance.msg_register(TEXT)
182 | def reply(msg):
183 | return msg['Text']
184 |
185 | newInstance.run()
186 | ```
187 |
188 | ### 退出及登陆完成后调用特定方法
189 |
190 | 登陆完成后的方法需要赋值在`loginCallback`中。
191 |
192 | 而退出后的方法需要赋值在`exitCallback`中。
193 |
194 | ```python
195 | import time
196 |
197 | import itchat
198 |
199 | def lc():
200 | print('finish login')
201 | def ec():
202 | print('exit')
203 |
204 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
205 | time.sleep(3)
206 | itchat.logout()
207 | ```
208 |
209 | 若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。
210 |
211 | ## 问题和建议
212 |
213 | 如果有什么问题或者建议都可以在这个[Issue][issue#1]和我讨论
214 |
215 | 或者也可以在gitter上交流:[![Gitter][gitter-picture]][gitter]
216 |
217 | 当然也可以加入我们新建的QQ群讨论:549762872
218 |
219 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
220 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
221 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
222 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
223 | [english-version]: https://github.com/littlecodersh/ItChat/blob/master/README_EN.md
224 | [document]: https://itchat.readthedocs.org/zh/latest/
225 | [tutorial2]: http://python.jobbole.com/86532/
226 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
227 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
228 | [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/
229 | [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/
230 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
231 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
232 | [littlecodersh]: https://github.com/littlecodersh
233 | [tempdban]: https://github.com/tempdban
234 | [Chyroc]: https://github.com/Chyroc
235 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
236 | [zixia-wechaty]: https://github.com/zixia/wechaty
237 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
238 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
239 |
--------------------------------------------------------------------------------
/docs/intro/index.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 | 如今微信已经成为了个人社交的很大一部分,希望这个项目能够帮助你扩展你的个人的微信号、方便自己的生活。
12 |
13 | ## 安装
14 |
15 | 可以通过本命令安装itchat:
16 |
17 | ```python
18 | pip install itchat
19 | ```
20 |
21 | ## 简单入门实例
22 |
23 | 有了itchat,如果你想要给文件传输助手发一条信息,只需要这样:
24 |
25 | ```python
26 | import itchat
27 |
28 | itchat.auto_login()
29 |
30 | itchat.send('Hello, filehelper', toUserName='filehelper')
31 | ```
32 |
33 | 如果你想要回复发给自己的文本消息,只需要这样:
34 |
35 | ```python
36 | import itchat
37 |
38 | @itchat.msg_register(itchat.content.TEXT)
39 | def text_reply(msg):
40 | return msg['Text']
41 |
42 | itchat.auto_login()
43 | itchat.run()
44 | ```
45 |
46 | 一些进阶应用可以在下面的开源机器人的源码和进阶应用中看到,或者你也可以阅览[文档][document]。
47 |
48 | ## 试一试
49 |
50 | 这是一个基于这一项目的[开源小机器人][robot-source-code],百闻不如一见,有兴趣可以尝试一下。
51 |
52 | ![QRCode][robot-qr]
53 |
54 | ## 截屏
55 |
56 | ![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
57 |
58 | ## 进阶应用
59 |
60 | ### 各类型消息的注册
61 |
62 | 通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
63 |
64 | ```python
65 | #coding=utf8
66 | import itchat, time
67 | from itchat.content import *
68 |
69 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
70 | def text_reply(msg):
71 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
72 |
73 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
74 | def download_files(msg):
75 | msg['Text'](msg['FileName'])
76 | return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
77 |
78 | @itchat.msg_register(FRIENDS)
79 | def add_friend(msg):
80 | itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
81 | itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
82 |
83 | @itchat.msg_register(TEXT, isGroupChat=True)
84 | def text_reply(msg):
85 | if msg['isAt']:
86 | itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
87 |
88 | itchat.auto_login(True)
89 | itchat.run()
90 | ```
91 |
92 | ### 命令行二维码
93 |
94 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
95 |
96 | ```python
97 | itchat.auto_login(enableCmdQR=True)
98 | ```
99 |
100 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
101 |
102 | ```python
103 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
104 | itchat.auto_login(enableCmdQR=2)
105 | ```
106 |
107 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
108 |
109 | ```python
110 | itchat.auto_login(enableCmdQR=-1)
111 | ```
112 |
113 | ### 退出程序后暂存登陆状态
114 |
115 | 通过如下命令登陆,即使程序关闭,一定时间内重新开启也可以不用重新扫码。
116 |
117 | ```python
118 | itchat.auto_login(hotReload=True)
119 | ```
120 |
121 | ### 用户搜索
122 |
123 | 使用`search_friends`方法可以搜索用户,有四种搜索方式:
124 | 1. 仅获取自己的用户信息
125 | 2. 获取特定`UserName`的用户信息
126 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
127 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
128 |
129 | 其中三、四项可以一同使用,下面是示例程序:
130 |
131 | ```python
132 | # 获取自己的用户信息,返回自己的属性字典
133 | itchat.search_friends()
134 | # 获取特定UserName的用户信息
135 | itchat.search_friends(userName='@abcdefg1234567')
136 | # 获取任何一项等于name键值的用户
137 | itchat.search_friends(name='littlecodersh')
138 | # 获取分别对应相应键值的用户
139 | itchat.search_friends(wechatAccount='littlecodersh')
140 | # 三、四项功能可以一同使用
141 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
142 | ```
143 |
144 | 关于公众号、群聊的获取与搜索在文档中有更加详细的介绍。
145 |
146 | ### 附件的下载与发送
147 |
148 | itchat的附件下载方法存储在msg的Text键中。
149 |
150 | 发送的文件的文件名(图片给出的默认文件名)都存储在msg的FileName键中。
151 |
152 | 下载方法接受一个可用的位置参数(包括文件名),并将文件相应的存储。
153 |
154 | ```python
155 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
156 | def download_files(msg):
157 | msg['Text'](msg['FileName'])
158 | itchat.send('@%s@%s'%('img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']), msg['FromUserName'])
159 | return '%s received'%msg['Type']
160 | ```
161 |
162 | 如果你不需要下载到本地,仅想要读取二进制串进行进一步处理可以不传入参数,方法将会返回图片的二进制串。
163 |
164 | ```python
165 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
166 | def download_files(msg):
167 | with open(msg['FileName'], 'wb') as f:
168 | f.write(msg['Text']())
169 | ```
170 |
171 | ### 用户多开
172 |
173 | 使用如下命令可以完成多开的操作:
174 |
175 | ```python
176 | import itchat
177 |
178 | newInstance = itchat.new_instance()
179 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
180 |
181 | @newInstance.msg_register(TEXT)
182 | def reply(msg):
183 | return msg['Text']
184 |
185 | newInstance.run()
186 | ```
187 |
188 | ### 退出及登陆完成后调用特定方法
189 |
190 | 登陆完成后的方法需要赋值在`loginCallback`中。
191 |
192 | 而退出后的方法需要赋值在`exitCallback`中。
193 |
194 | ```python
195 | import time
196 |
197 | import itchat
198 |
199 | def lc():
200 | print('finish login')
201 | def ec():
202 | print('exit')
203 |
204 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
205 | time.sleep(3)
206 | itchat.logout()
207 | ```
208 |
209 | 若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。
210 |
211 | ## 问题和建议
212 |
213 | 如果有什么问题或者建议都可以在这个[Issue][issue#1]和我讨论
214 |
215 | 或者也可以在gitter上交流:[![Gitter][gitter-picture]][gitter]
216 |
217 | 当然也可以加入我们新建的QQ群讨论:549762872
218 |
219 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
220 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
221 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
222 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
223 | [english-version]: https://github.com/littlecodersh/ItChat/blob/master/README_EN.md
224 | [document]: https://itchat.readthedocs.org/zh/latest/
225 | [tutorial2]: http://python.jobbole.com/86532/
226 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
227 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
228 | [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/
229 | [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/
230 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
231 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
232 | [littlecodersh]: https://github.com/littlecodersh
233 | [tempdban]: https://github.com/tempdban
234 | [Chyroc]: https://github.com/Chyroc
235 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
236 | [zixia-wechaty]: https://github.com/zixia/wechaty
237 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
238 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
239 |
--------------------------------------------------------------------------------
/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 | ![QRCode][robot-qr]
55 |
56 | ## 截屏
57 |
58 | ![file-autoreply][robot-demo-file] ![login-page][robot-demo-login]
59 |
60 | ## 进阶应用
61 |
62 | ### 各类型消息的注册
63 |
64 | 通过如下代码,微信已经可以就日常的各种信息进行获取与回复。
65 |
66 | ```python
67 | #coding=utf8
68 | import itchat, time
69 | from itchat.content import *
70 |
71 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
72 | def text_reply(msg):
73 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
74 |
75 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
76 | def download_files(msg):
77 | msg['Text'](msg['FileName'])
78 | return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
79 |
80 | @itchat.msg_register(FRIENDS)
81 | def add_friend(msg):
82 | itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
83 | itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
84 |
85 | @itchat.msg_register(TEXT, isGroupChat=True)
86 | def text_reply(msg):
87 | if msg['isAt']:
88 | itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
89 |
90 | itchat.auto_login(True)
91 | itchat.run()
92 | ```
93 |
94 | ### 命令行二维码
95 |
96 | 通过以下命令可以在登陆的时候使用命令行显示二维码:
97 |
98 | ```python
99 | itchat.auto_login(enableCmdQR=True)
100 | ```
101 |
102 | 部分系统可能字幅宽度有出入,可以通过将enableCmdQR赋值为特定的倍数进行调整:
103 |
104 | ```python
105 | # 如部分的linux系统,块字符的宽度为一个字符(正常应为两字符),故赋值为2
106 | itchat.auto_login(enableCmdQR=2)
107 | ```
108 |
109 | 默认控制台背景色为暗色(黑色),若背景色为浅色(白色),可以将enableCmdQR赋值为负值:
110 |
111 | ```python
112 | itchat.auto_login(enableCmdQR=-1)
113 | ```
114 |
115 | ### 退出程序后暂存登陆状态
116 |
117 | 通过如下命令登陆,即使程序关闭,一定时间内重新开启也可以不用重新扫码。
118 |
119 | ```python
120 | itchat.auto_login(hotReload=True)
121 | ```
122 |
123 | ### 用户搜索
124 |
125 | 使用`search_friends`方法可以搜索用户,有四种搜索方式:
126 | 1. 仅获取自己的用户信息
127 | 2. 获取特定`UserName`的用户信息
128 | 3. 获取备注、微信号、昵称中的任何一项等于`name`键值的用户
129 | 4. 获取备注、微信号、昵称分别等于相应键值的用户
130 |
131 | 其中三、四项可以一同使用,下面是示例程序:
132 |
133 | ```python
134 | # 获取自己的用户信息,返回自己的属性字典
135 | itchat.search_friends()
136 | # 获取特定UserName的用户信息
137 | itchat.search_friends(userName='@abcdefg1234567')
138 | # 获取任何一项等于name键值的用户
139 | itchat.search_friends(name='littlecodersh')
140 | # 获取分别对应相应键值的用户
141 | itchat.search_friends(wechatAccount='littlecodersh')
142 | # 三、四项功能可以一同使用
143 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
144 | ```
145 |
146 | 关于公众号、群聊的获取与搜索在文档中有更加详细的介绍。
147 |
148 | ### 附件的下载与发送
149 |
150 | itchat的附件下载方法存储在msg的Text键中。
151 |
152 | 发送的文件的文件名(图片给出的默认文件名)都存储在msg的FileName键中。
153 |
154 | 下载方法接受一个可用的位置参数(包括文件名),并将文件相应的存储。
155 |
156 | ```python
157 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
158 | def download_files(msg):
159 | msg['Text'](msg['FileName'])
160 | itchat.send('@%s@%s'%('img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']), msg['FromUserName'])
161 | return '%s received'%msg['Type']
162 | ```
163 |
164 | 如果你不需要下载到本地,仅想要读取二进制串进行进一步处理可以不传入参数,方法将会返回图片的二进制串。
165 |
166 | ```python
167 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
168 | def download_files(msg):
169 | with open(msg['FileName'], 'wb') as f:
170 | f.write(msg['Text']())
171 | ```
172 |
173 | ### 用户多开
174 |
175 | 使用如下命令可以完成多开的操作:
176 |
177 | ```python
178 | import itchat
179 |
180 | newInstance = itchat.new_instance()
181 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
182 |
183 | @newInstance.msg_register(TEXT)
184 | def reply(msg):
185 | return msg['Text']
186 |
187 | newInstance.run()
188 | ```
189 |
190 | ### 退出及登陆完成后调用特定方法
191 |
192 | 登陆完成后的方法需要赋值在`loginCallback`中。
193 |
194 | 而退出后的方法需要赋值在`exitCallback`中。
195 |
196 | ```python
197 | import time
198 |
199 | import itchat
200 |
201 | def lc():
202 | print('finish login')
203 | def ec():
204 | print('exit')
205 |
206 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
207 | time.sleep(3)
208 | itchat.logout()
209 | ```
210 |
211 | 若不设置loginCallback的值,则将会自动删除二维码图片并清空命令行显示。
212 |
213 | ## 常见问题与解答
214 |
215 | Q: 为什么中文的文件没有办法上传?
216 |
217 | A: 这是由于`requests`的编码问题导致的。若需要支持中文文件传输,将[fields.py][fields.py-2](py3版本见[这里][fields.py-3])文件放入requests包的packages/urllib3下即可
218 |
219 | Q: 为什么我在设定了`itchat.auto_login()`的`enableCmdQR`为`True`后还是没有办法在命令行显示二维码?
220 |
221 | A: 这是由于没有安装可选的包`pillow`,可以使用右边的命令安装:`pip install pillow`
222 |
223 | Q: 如何通过这个包将自己的微信号变为控制器?
224 |
225 | A: 有两种方式:发送、接受自己UserName的消息;发送接收文件传输助手(filehelper)的消息
226 |
227 | Q: 为什么我发送信息的时候部分信息没有成功发出来?
228 |
229 | A: 有些账号是天生无法给自己的账号发送信息的,建议使用`filehelper`代替。
230 |
231 | ## 作者
232 |
233 | [LittleCoder][littlecodersh]: 构架及维护Python2 Python3版本。
234 |
235 | [tempdban][tempdban]: 协议、构架及日常维护。
236 |
237 | [Chyroc][Chyroc]: 完成第一版本的Python3构架。
238 |
239 | ## 类似项目
240 |
241 | [liuwons/wxBot][liuwons-wxBot]: 类似的基于Python的微信机器人
242 |
243 | [zixia/wechaty][zixia-wechaty]: 基于Javascript(ES6)的微信个人账号机器人NodeJS框架/库
244 |
245 | [sjdy521/Mojo-Weixin][Mojo-Weixin]: 使用Perl语言编写的微信客户端框架,可通过插件提供基于HTTP协议的api接口供其他语言调用
246 |
247 | ## 问题和建议
248 |
249 | 如果有什么问题或者建议都可以在这个[Issue][issue#1]和我讨论
250 |
251 | 或者也可以在gitter上交流:[![Gitter][gitter-picture]][gitter]
252 |
253 | 当然也可以加入我们新建的QQ群讨论:549762872
254 |
255 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
256 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
257 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
258 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
259 | [english-version]: https://github.com/littlecodersh/ItChat/blob/master/README_EN.md
260 | [itchatmp]: https://github.com/littlecodersh/itchatmp
261 | [document]: https://itchat.readthedocs.org/zh/latest/
262 | [tutorial2]: http://python.jobbole.com/86532/
263 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
264 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
265 | [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/
266 | [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/
267 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
268 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
269 | [littlecodersh]: https://github.com/littlecodersh
270 | [tempdban]: https://github.com/tempdban
271 | [Chyroc]: https://github.com/Chyroc
272 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
273 | [zixia-wechaty]: https://github.com/zixia/wechaty
274 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
275 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
276 |
--------------------------------------------------------------------------------
/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 | * 通过微信操作的音乐播放器([源码][demo-pcmusicviawechat])
27 | * 消息内容与对象可自定义的消息群发助手([源码][demo-wechatsmartwish])
28 | * 特定好友删除检测([源码][demo-wechatcheckfriend])
29 |
30 | 使用微信协议完成机器人较为平常,如果对具体细节感兴趣,可以添加个人号`littlecodersh`并回复“源代码”。
31 |
32 | 本文主要基于微信API的第三方包itchat,你可以在[项目主页][main-page]获取更多信息。
33 |
34 | ## 本部分所需环境
35 |
36 | 本文是这一教程的第二部分,需要基本的pip可用的Python环境。
37 |
38 | 本教程使用的环境如下:
39 | * Windows 8.1 (其他平台也可用)
40 | * Python 2 or 3
41 | * 微信版本6.3.25
42 |
43 | ## 微信控制器
44 |
45 | ![pic0][demo-pcmusicviawechat-0] ![pic1][demo-pcmusicviawechat-1]
46 |
47 | 在项目主页上,专门有人就微信作为智能家居入口向我提出了很多想法。
48 |
49 | 如果微信可以作为控制器,就可以不必自制手机端客户端的麻烦。
50 |
51 | 其实这个需求实现起来非常简单,这里我借鉴了yaphone的[RasWxMusicbox][yaphone-raswxmusicbox],使用了其中部分的代码作为演示。
52 |
53 | 这是一个通过微信控制电脑播放音乐的小项目,那么主要就是三个功能:
54 | * 输入“帮助”,显示帮助
55 | * 输入“关闭”,关闭音乐播放
56 | * 输入具体歌名,进入歌曲的选择
57 |
58 | 换成代码就是这样一个逻辑:
59 | ```python
60 | if msg == u'关闭':
61 | close_music()
62 | print(u'音乐已关闭')
63 | if msg == u'帮助':
64 | print(u'帮助信息')
65 | else:
66 | print(interact_select_song(msg))
67 | ```
68 |
69 | 那么现在需要解决的就是如何关闭音乐,如何选择音乐和如何使用微信交互。
70 |
71 | 关闭音乐我们这里使用打开空文件的方式,而选择音乐我们使用网易云音乐的API完成:
72 | ```python
73 | import os
74 | # 通过该命令安装该API: pip install NetEaseMusicApi
75 | from NetEaseMusicApi import interact_select_song
76 |
77 | with open('stop.mp3', 'w') as f: pass
78 | def close_music():
79 | os.startfile('stop.mp3')
80 | ```
81 |
82 | 而微信的调用可以通过itchat包简单的完成,这里要注意的是:
83 | * 有些账号无法与自己通信,所以我们选择与文件传输助手(filehelper)通信
84 | * 为了防止对于其他消息的响应,我们在第一行过滤了无关信息
85 | * itchat.run的选项分别为允许热拔插,方便调试
86 |
87 | ```python
88 | # 接上段程序
89 | # 通过该命令安装该API: pip install itchat
90 | import itchat
91 |
92 | @itchat.msg_register(itchat.content.TEXT)
93 | def music_player(msg):
94 | if msg['ToUserName'] != 'filehelper': return
95 | if msg['Text'] == u'关闭':
96 | close_music()
97 | itchat.send(u'音乐已关闭', 'filehelper')
98 | if msg['Text'] == u'帮助':
99 | itchat.send(u'帮助信息', 'filehelper')
100 | else:
101 | itchat.send(interact_select_song(msg['Text']), 'filehelper')
102 |
103 | itchat.auto_login(True)
104 | itchat.send(HELP_MSG, 'filehelper')
105 | itchat.run()
106 | ```
107 |
108 | itchat对常用功能都做好了封装,调用还是非常容易的。
109 |
110 | 完整的程序我放在了[gist][demo-pcmusicviawechat]上面,使用时不要忘记安装第三方包。
111 |
112 | 通过与文件传输助手的交互,微信就能够轻松变成其他程序的入口。
113 |
114 | ## 群发助手
115 |
116 | 在短信的时代,逢年过节都会需要接收和发送大量的短信。
117 |
118 | 虽然自己也看到短信就烦,但不发又怕会错过什么。
119 |
120 | 所以当时就产生了各式各样的群发工具,最简单的比如在消息中加入昵称,让人感觉不像群发。
121 |
122 | 不过可惜的是,微信自带的群发助手真的只是群发。
123 |
124 | 当然,稍加操作,一切皆有可能。
125 |
126 | 例如在消息中加入昵称:
127 | * 通过`get_friends`方法可以轻松获取所有的好友(好友首位是自己)
128 | * 基于不同的好友可以发送不同的消息
129 | * 这条程序运行后是真的会发消息出去,如果只是演示目的,把`itchat.send`改为`print`即可
130 |
131 | ```python
132 | #coding=utf8
133 | import itchat, time
134 |
135 | itchat.auto_login(True)
136 |
137 | SINCERE_WISH = u'祝%s新年快乐!'
138 |
139 | friendList = itchat.get_friends(update=True)[1:]
140 | for friend in friendList:
141 | # 如果是演示目的,把下面的方法改为print即可
142 | itchat.send(SINCERE_WISH % (friend['DisplayName']
143 | or friend['NickName']), friend['UserName'])
144 | time.sleep(.5)
145 | ```
146 |
147 | 又例如给特定的人发送特定的消息。
148 |
149 | 我们这里通过群聊实现,划定一个群聊,在群聊内则私信发送祝福。
150 | * 如果仅是创建群聊不说话,对方是不会有提示的
151 | * 群聊如果不**保存到通讯录**,是无法在各设备之间同步的(所以itchat也无法读取到)
152 | * 群聊在被获取的时候不会自带用户列表,所以需要使用`update_chatroom`更新用户列表
153 | * 当然,如果只是演示目的,把`itchat.send`改为`print`即可
154 |
155 | ```python
156 | #coding=utf8
157 | import itchat, time
158 |
159 | itchat.auto_login(True)
160 |
161 | REAL_SINCERE_WISH = u'祝%s新年快乐!!'
162 |
163 | chatroomName='wishgroup'
164 | itchat.get_chatrooms(update=True)
165 | chatrooms = itchat.search_chatrooms(name=chatroomName)
166 | if chatrooms is None:
167 | print(u'没有找到群聊:' + chatroomName)
168 | else:
169 | chatroom = itchat.update_chatroom(chatrooms[0]['UserName'])
170 | for friend in chatroom['MemberList']:
171 | friend = itchat.search_friends(userName=friend['UserName'])
172 | # 如果是演示目的,把下面的方法改为print即可
173 | itchat.send(REAL_SINCERE_WISH % (friend['DisplayName']
174 | or friend['NickName']), friend['UserName'])
175 | time.sleep(.5)
176 | ```
177 |
178 | 所以我的通讯录里会有从来不用的客户群、教师群什么的。
179 |
180 | 完整的程序我放在了[gist][demo-wechatsmartwish]上面,使用时不要忘记安装第三方包。
181 |
182 | 当然,为了防止误操作,完整程序中我把所有的`itchat.send`换成了`print`。
183 |
184 | 另外,不只有文字可以发送,文件、图片也都是可行的,具体操作见itchat的[文档][document]了。
185 |
186 | itchat获取微信可以获取到的各种内容也都非常方便。
187 |
188 | 其余的例如生日,节日什么的就看具体需求了。
189 |
190 | ## 好友删除检测
191 |
192 | ![pic][demo-wechatcheckfriend]
193 |
194 | 有时候我们会想知道某个好友有没有删除自己或者把自己拉入黑名单。
195 |
196 | 这一操作使用itchat也会变的非常简单。
197 |
198 | 原理的话,在于将好友拉入群聊时,非好友和黑名单好友不会被拉入群聊。
199 |
200 | 所以群聊的返回值中就有了好友与你关系的数据。
201 |
202 | 另外,群聊在第一次产生普通消息时才会被除创建者以外的人发现的(系统消息不算普通消息)。
203 |
204 | 这样,就可以隐蔽的完成好友检测。
205 |
206 | 写成代码的话,这个操作就是这样的:(只是演示,不能运行,运行版本在段末)
207 |
208 | ```python
209 | chatroomUserName = '@1234567'
210 | friend = itchat.get_friends()[1]
211 |
212 | r = itchat.add_member_into_chatroom(chatroomUserName, [friend])
213 | if r['BaseResponse']['ErrMsg'] == '':
214 | status = r['MemberList'][0]['MemberStatus']
215 | itchat.delete_member_from_chatroom(chatroom['UserName'], [friend])
216 | return { 3: u'该好友已经将你加入黑名单。',
217 | 4: u'该好友已经将你删除。', }.get(status,
218 | u'该好友仍旧与你是好友关系。')
219 | ```
220 |
221 | 其中,通过`add_member_into_chatroom`操作获取我们需要的返回值,即可得到好友的状态。
222 |
223 | 同样的,这次我们也将文件传输助手作为终端,具体方法与控制器一节类似。
224 |
225 | 这次我们确定的交互方式是接收“名片”消息,并判断名片中的好友与自己的关系。
226 |
227 | 那么获取名片信息的内容可以这么写:
228 |
229 | ```python
230 | import itchat
231 |
232 | @itchat.msg_register(itchat.content.CARD)
233 | def get_friend(msg):
234 | if msg['ToUserName'] != 'filehelper': return
235 | friendStatus = get_friend_status(msg['RecommendInfo'])
236 | itchat.send(friendStatus['NickName'], 'filehelper')
237 |
238 | itchat.auto_login(True)
239 | itchat.run()
240 | ```
241 |
242 | 那么我们所需要的所有部分就都解决了,下面将他们组合起来即可。
243 |
244 | 完整的程序我放在了[gist][demo-wechatcheckfriend]上面,使用时不要忘记安装第三方包。
245 |
246 | 在网页版微信的接口受到限制之前完全可以批量进行这一操作,检测哪些好友删除了自己。
247 |
248 | 但目前显然操作存在频率限制,所以只能做一些变通了。
249 |
250 | ## 之后的内容
251 |
252 | 到这里这一篇文章的主要内容就结束了。
253 |
254 | 主要从微信作为终端使用、自定义的消息交互、微信协议研究三方面开了一个简单的头。
255 |
256 | 其余有一些过于大众,如机器人,就不再赘述。
257 |
258 | 而另一些,需要一定的基础或者不适合分享,就留给各位自行研究。
259 |
260 | 如果要留个悬念,可以想象添加好友的方法status传2,轻松实现好友病毒式扩张。
261 |
262 | 利用微信的API可以做很多事情,文档我放在[这里][document],祝好运!
263 |
264 | ## 结束语
265 |
266 | 希望读完这篇文章能对你有帮助,有什么不足之处万望指正(鞠躬)。
267 |
268 | 有什么想法或者想要关注我的更新,欢迎来[**Github**](https://github.com/littlecodersh/ItChat)上***Star***或者***Fork***。
269 |
270 | 160928
271 |
272 | LittleCoder
273 |
274 | EOF
275 |
276 |
277 | [document]: http://itchat.readthedocs.io/zh/latest/
278 | [main-page]: https://github.com/littlecodersh/ItChat
279 | [tutoral#1]: http://python.jobbole.com/84918/
280 | [yaphone-raswxmusicbox]: https://github.com/yaphone/RasWxMusicbox
281 | [demo-pcmusicviawechat]: https://gist.github.com/littlecodersh/8468afbbb8d34c0c0e6848b6f9009c4c
282 | [demo-wechatsmartwish]: https://gist.github.com/littlecodersh/ae13ed93e0e8f3c820226fc6871f436d
283 | [demo-wechatcheckfriend]: https://gist.github.com/littlecodersh/3fef7d2afb2d502e4735be083c9f79e1
284 | [demo-pcmusicviawechat-0]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/Tutorial/2/demo-pcmusicviawechat-0.png?imageView/2/w/200/
285 | [demo-pcmusicviawechat-1]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/Tutorial/2/demo-pcmusicviawechat-1.png?imageView/2/w/200/
286 | [demo-wechatcheckfriend]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat/Tutorial/2/demo-wechatcheckfriend.png?imageView/2/w/200/
287 |
--------------------------------------------------------------------------------
/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 | 在终端中输入以下命令,完成微信的API包itchat的安装。
29 |
30 | 我们这里使用python3的环境(python2也是可行的):
31 |
32 | ```bash
33 | sudo pip3 install itchat --upgrade
34 | ```
35 |
36 | 通过该命令判断是否安装成功:
37 |
38 | ```bash
39 | python3 -c "import itchat"
40 | ```
41 |
42 | 如果没有报错信息说明你已经将实验环境安装完成。
43 |
44 | ![install][install]
45 |
46 | ## 三、实验原理
47 |
48 | 通过微信的Python接口itchat获取微信消息。
49 |
50 | 将微信消息传输到机器人接口(这里以图灵为例),获取机器人的返回消息。
51 |
52 | 将返回消息返回给微信消息的发送人。
53 |
54 | 实现将微信个人号变为聊天机器人的目的。
55 |
56 | ## 四、实验步骤
57 |
58 | ### 0. 基础知识
59 |
60 | 为了照顾一些从未使用过Python的新用户与使用其他语言的用户,这里简单的讲一下以下的代码如何使用。
61 |
62 | 下面的每一段描述都给出了相应的测试代码,如果没有特殊说明这段代码可以这样使用:
63 |
64 | 打开桌面的Xfce终端,先将目录通过以下命令切到桌面。
65 |
66 | ```bash
67 | cd Desktop
68 | ```
69 |
70 | 之后使用gedit编辑器编辑我们的主程序。
71 |
72 | 你也完全可以使用vim,会使用vim的话想必也知道这里应该输入什么命令了。
73 |
74 | ```bash
75 | gedit test.py
76 | ```
77 |
78 | 最后将给出的代码复制进编辑器,保存并退出,使用如下命令就可以使用了。
79 |
80 | ```bash
81 | python3 test.py
82 | ```
83 |
84 | 那么,就让我们开始正式进入Python操作微信的探索之旅吧。
85 |
86 | ### 1. 实现微信消息的获取
87 |
88 | itchat的注册时根据类型注册的。
89 |
90 | 在获取相应类型的信息时会调用该函数。
91 |
92 | 我们现在只需要获取最简单的文本消息,那么只需要这样注册:
93 |
94 | ```python
95 | import itchat
96 |
97 | @itchat.msg_register(itchat.content.TEXT)
98 | def print_content(msg):
99 | print(msg['Text'])
100 |
101 | itchat.auto_login()
102 | itchat.run()
103 | ```
104 |
105 | 其中第三行即注册的操作,通过装饰符将`print_content`注册为处理文本消息的函数。
106 |
107 | 微信有各种类型的数据,例如图片、语音、名片、分享等,也对应不同的注册参数:
108 | * 图片对应`itchat.content.PICTURE`
109 | * 语音对应`itchat.content.RECORDING`
110 | * 名片对应`itchat.content.CARD`
111 | * 其余的这里就不一一列举,更具体的内容可以自行搜索itchat阅读[文档][document]
112 |
113 | 执行命令
114 | ```
115 | python3 test.py
116 | ```
117 |
118 | 就可看到我们开始登陆微信:
119 |
120 | ![login][login]
121 |
122 | 扫码完成以后最基础的文本信息的接收就完成了,你可以尝试用他人的微信给自己发一条信息。
123 |
124 | 如果你不想要每次运行程序都扫码,可以在登陆命令中进行设置:
125 |
126 | ```python
127 | itchat.auto_login(hotReload=True)
128 | ```
129 |
130 | ### 2. 实现微信消息的发送
131 |
132 | 微信可以发送各类消息,文本、图片、文件等,不过我们现在只需要使用文本的发送。
133 |
134 | 其余的消息的发送有兴趣可以自行阅读。
135 |
136 | ```python
137 | itchat.send('Message Content', 'toUserName')
138 | ```
139 |
140 | 该发送消息的函数需要两个参数,消息的内容与接受者的UserName,即标识符。
141 |
142 | 那么我们试着向文件传输助手发送一条消息:
143 |
144 | ```python
145 | #coding=utf8
146 | import itchat
147 |
148 | itchat.auto_login(hotReload=True)
149 |
150 | # 注意实验楼环境的中文输入切换
151 | itchat.send(u'测试消息发送', 'filehelper')
152 | ```
153 |
154 | 打开手机看一下是否就完成了消息的发送。
155 |
156 | 保存代码后,执行命令:
157 |
158 | ```
159 | python3 test.py
160 | ```
161 |
162 | 扫描登录后的效果如下:
163 |
164 | ![send-hello][send-hello]
165 |
166 | 当然,还有一种更加快捷的回复方法就是在注册函数中直接回复。
167 |
168 | 例如下面的例子将会将文本消息原封不动的返回。
169 |
170 | ```python
171 | import itchat
172 |
173 | @itchat.msg_register(itchat.content.TEXT)
174 | def print_content(msg):
175 | return msg['Text']
176 |
177 | itchat.auto_login()
178 | itchat.run()
179 | ```
180 |
181 | 这种方式显然更加直观也更加简单(不需要输入接受者的UserName)
182 |
183 | 我们本次实践将会采用这种方式。
184 |
185 | ### 3. 实现最简单的与图灵机器人的交互
186 |
187 | 要做一个能够与人交流的机器人有很多种方法,最简单的莫过于使用他人提供的接口。
188 |
189 | 我们这里以图灵机器人为例,演示这一功能。
190 |
191 | 图灵机器人简单而言就是以一定的规则给图灵的服务器发送数据包(包含你对他说的话)
192 |
193 | 图灵的服务器会以一定的规则给你返回数据包(包含他回复你的话)
194 |
195 | 你需要一个Tuling Key来告诉图灵服务器你有权和他对话,我这里免费提供一些:
196 |
197 | ```bash
198 | 8edce3ce905a4c1dbb965e6b35c3834d
199 | eb720a8970964f3f855d863d24406576
200 | 1107d5601866433dba9599fac1bc0083
201 | 71f28bf79c820df10d39b4074345ef8c
202 | ```
203 |
204 | 下面我做一个配置图灵机器人的简单介绍,你想要自行了解或者申请Tuling Key可以看[这里][tuling]
205 |
206 | 发送的规则简而言之是这样的:
207 |
208 | ```json
209 | {
210 | 'key' : 'TULING_KEY',
211 | 'info' : 'YOUR_MSG',
212 | 'userid' : 'USERID',
213 | }
214 | ```
215 |
216 | 其中userId是用户的标志,让机器人知道你是你。(也就是一个Tuling Key可以有多个用户)
217 |
218 | 而返回的内容基本是这样的:
219 |
220 | ```json
221 | {
222 | 'code': 0,
223 | 'text': 'RETURN_MSG',
224 | }
225 | ```
226 |
227 | 我们需要的内容就在text键里面。
228 |
229 | 这里我们使用requests包完成整个操作(已经包含在itchat包的安装中了)。
230 |
231 | 最后值得一提的就是这是一个post请求,那么直接上代码应该比我絮絮叨叨的说要直观很多。
232 |
233 | ```python
234 | #coding=utf8
235 | import requests
236 |
237 | apiUrl = 'http://www.tuling123.com/openapi/api'
238 | data = {
239 | 'key' : '8edce3ce905a4c1dbb965e6b35c3834d', # 如果这个Tuling Key不能用,那就换一个
240 | 'info' : 'hello', # 这是我们发出去的消息
241 | 'userid' : 'wechat-robot', # 这里你想改什么都可以
242 | }
243 | # 我们通过如下命令发送一个post请求
244 | r = requests.post(apiUrl, data=data).json()
245 |
246 | # 让我们打印一下返回的值,看一下我们拿到了什么
247 | print(r)
248 | ```
249 |
250 | 我们可以看到他回复了你好。
251 |
252 | ![reply-hello][reply-hello]
253 |
254 | 至此我们已经理解并掌握了所有需要的内容,下面将其组装起来即可。
255 |
256 | ## 五、实验程序
257 |
258 | 我先从概念上说一下组装是一个怎么样的过程。
259 |
260 | 当然,如果你觉得代码更直观,我也在代码中为你写好了注释。
261 |
262 | 这里我们首先将与图灵服务器的交互定义为一个函数。
263 |
264 | 我们需要这个函数接收我们要发送给图灵的消息,返回图灵返回给我们的消息。
265 |
266 | 再将与图灵交互并返回图灵返回结果的操作写成函数并在itchat中注册。
267 |
268 | 最后启动itchat,我们的程序就完成了。
269 |
270 | ```python
271 | #coding=utf8
272 | import requests
273 | import itchat
274 |
275 | KEY = '8edce3ce905a4c1dbb965e6b35c3834d'
276 |
277 | def get_response(msg):
278 | # 这里我们就像在“3. 实现最简单的与图灵机器人的交互”中做的一样
279 | # 构造了要发送给服务器的数据
280 | apiUrl = 'http://www.tuling123.com/openapi/api'
281 | data = {
282 | 'key' : KEY,
283 | 'info' : msg,
284 | 'userid' : 'wechat-robot',
285 | }
286 | try:
287 | r = requests.post(apiUrl, data=data).json()
288 | # 字典的get方法在字典没有'text'值的时候会返回None而不会抛出异常
289 | return r.get('text')
290 | # 为了防止服务器没有正常响应导致程序异常退出,这里用try-except捕获了异常
291 | # 如果服务器没能正常交互(返回非json或无法连接),那么就会进入下面的return
292 | except:
293 | # 将会返回一个None
294 | return
295 |
296 | # 这里是我们在“1. 实现微信消息的获取”中已经用到过的同样的注册方法
297 | @itchat.msg_register(itchat.content.TEXT)
298 | def tuling_reply(msg):
299 | # 为了保证在图灵Key出现问题的时候仍旧可以回复,这里设置一个默认回复
300 | defaultReply = 'I received: ' + msg['Text']
301 | # 如果图灵Key出现问题,那么reply将会是None
302 | reply = get_response(msg['Text'])
303 | # a or b的意思是,如果a有内容,那么返回a,否则返回b
304 | # 有内容一般就是指非空或者非None,你可以用`if a: print('True')`来测试
305 | return reply or defaultReply
306 |
307 | # 为了让实验过程更加方便(修改程序不用多次扫码),我们使用热启动
308 | itchat.auto_login(hotReload=True)
309 | itchat.run()
310 | ```
311 |
312 | ## 六、实验结果
313 |
314 | 在本机上通过如下命令可以运行该程序
315 |
316 | ```bash
317 | python3 main.py
318 | ```
319 |
320 | 扫码登陆后程序就成功运行了。
321 |
322 | 之后在手机上使用别的账号给自己的微信号发送消息即可获得机器人的回复。
323 |
324 | 这里给出使用的效果图:
325 |
326 | ![demo][demo]
327 |
328 | 如果你想要通过与其他用户的交互完成该操作,自行在注册的函数中进行修改即可。
329 |
330 | 如果你想要进一步了解使用Python控制微信的细节,你也可以去到项目主页[itchat][itchat]。
331 |
332 | 或者直接阅读[文档][document]也是不错的选择。
333 |
334 | 如果你的本地环境并非Python3也没有关系,itchat同样完美支持Python2。
335 |
336 | ## 七、代码获取
337 |
338 | 我将整个项目目录做了一个打包,你可以直接下载后运行。
339 |
340 | 你可以在[这里][code-package]下载。
341 |
342 | 如果有什么问题,欢迎在我的[主页][author]留言或者[邮件][email]联系我。
343 |
344 | [vip]: https://www.shiyanlou.com/courses/684
345 | [author]: https://github.com/littlecodersh
346 | [install]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/install.png?imageView/2/h/300/
347 | [tuling]: http://tuling123.com/help/h_cent_webapi.jhtml
348 | [login]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/login.png?imageView/2/h/300/
349 | [send-hello]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/send-hello.png?imageView/2/h/400
350 | [reply-hello]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/reply-hello.png?imageView/2/h/300/
351 | [demo]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/demo.png?imageView/2/h/400/
352 | [code-package]: http://7xrip4.com1.z0.glb.clouddn.com/shiyanlou/itchat/2/main.py
353 | [email]: mailto:i7meavnktqegm1b@qq.com
354 | [itchat]: https://github.com/littlecodersh/itchat
355 | [document]: http://itchat.readthedocs.io/zh/latest/
356 |
--------------------------------------------------------------------------------
/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 | itchat.send(msg['Text'], msg['FromUserName'])
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 | *Message register of various types*
60 |
61 | The following is a demo of how itchat is configured to fetch and reply daily information.
62 |
63 | .. code:: python
64 |
65 | #coding=utf8
66 | import itchat, time
67 | from itchat.content import *
68 |
69 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
70 | def text_reply(msg):
71 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
72 |
73 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
74 | def download_files(msg):
75 | msg['Text'](msg['FileName'])
76 | return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
77 |
78 | @itchat.msg_register(FRIENDS)
79 | def add_friend(msg):
80 | itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
81 | itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
82 |
83 | @itchat.msg_register(TEXT, isGroupChat=True)
84 | def text_reply(msg):
85 | if msg['isAt']:
86 | itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
87 |
88 | itchat.auto_login(True)
89 | itchat.run()
90 |
91 | *Command line QR Code*
92 |
93 | You can access the QR Code in command line through using this command:
94 |
95 | .. code:: python
96 |
97 | itchat.auto_login(enableCmdQR=True)
98 |
99 | Because of width of some character differs from systems, you may adjust the enableCmdQR to fix the problem.
100 |
101 | .. code:: python
102 |
103 | # for some linux system, width of block character is one instead of two, so enableCmdQR should be 2
104 | itchat.auto_login(enableCmdQR=2)
105 |
106 | Default background color of command line is dark (black), if it's not, you may set enableCmdQR to be negative:
107 |
108 | .. code:: python
109 |
110 | itchat.auto_login(enableCmdQR=-1)
111 |
112 | *Hot reload*
113 |
114 | By using the following command, you may reload the program without re-scan QRCode in some time.
115 |
116 | .. code:: python
117 |
118 | itchat.auto_login(hotReload=True)
119 |
120 | *User search*
121 |
122 | By using `search_friends`, you have four ways to search a user:
123 |
124 | 1. Get your own user information
125 | 2. Get user information through `UserName`
126 | 3. Get user information whose remark name or wechat account or nickname matches name key of the function
127 | 4. Get user information whose remark name, wechat account and nickname match what are given to the function
128 |
129 | Way 3, 4 can be used together, the following is the demo program:
130 |
131 | .. code:: python
132 |
133 | # get your own user information
134 | itchat.search_friends()
135 | # get user information of specific username
136 | itchat.search_friends(userName='@abcdefg1234567')
137 | # get user information of function 3
138 | itchat.search_friends(name='littlecodersh')
139 | # get user information of function 4
140 | itchat.search_friends(wechatAccount='littlecodersh')
141 | # combination of way 3, 4
142 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
143 |
144 | There are detailed information about searching and getting of massive platforms and chatrooms in document.
145 |
146 | *Download and send attachments*
147 |
148 | The attachment download function of itchat is in Text key of msg
149 |
150 | Name of the file (default name of picture) is in FileName key of msg
151 |
152 | Download function accept one location value (include the file name) and store attachment accordingly.
153 |
154 | .. code:: python
155 |
156 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
157 | def download_files(msg):
158 | msg['Text'](msg['FileName'])
159 | itchat.send('@%s@%s'%('img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']), msg['FromUserName'])
160 | return '%s received'%msg['Type']
161 |
162 | If you don't want a local copy of the picture, you may pass nothing to the function to get a binary string.
163 |
164 | .. code:: python
165 |
166 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
167 | def download_files(msg):
168 | with open(msg['FileName'], 'wb') as f:
169 | f.write(msg['Text']())
170 |
171 | *Multi instance*
172 |
173 | You may use the following commands to open multi instance.
174 |
175 | .. code:: python
176 |
177 | import itchat
178 |
179 | newInstance = itchat.new_instance()
180 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
181 |
182 | @newInstance.msg_register(TEXT)
183 | def reply(msg):
184 | return msg['Text']
185 |
186 | newInstance.run()
187 |
188 | *Set callback after login and logout*
189 |
190 | Callback of login and logout are set through `loginCallback` and `exitCallback`.
191 |
192 | .. code:: python
193 |
194 | import time
195 |
196 | import itchat
197 |
198 | def lc():
199 | print('finish login')
200 | def ec():
201 | print('exit')
202 |
203 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
204 | time.sleep(3)
205 | itchat.logout()
206 |
207 | If loginCallback is not set, qr picture will be deleted and cmd will be cleared.
208 |
209 | If you exit through phone, exitCallback will also be called.
210 |
211 | **FAQ**
212 |
213 | Q: Why I can't send files whose name is encoded in utf8?
214 |
215 | 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.
216 |
217 | Q: Why I still can't show QRCode with command line after I set enableCmdQr key to True in itchat.auto_login()?
218 |
219 | A: That's because you need to install optional site-package pillow, try this script: pip install pillow
220 |
221 | Q: How to use this package to use my wechat as an monitor?
222 |
223 | A: There are two ways: communicate with your own account or with filehelper.
224 |
225 | Q: Why sometimes I can't send messages?
226 |
227 | A: Some account simply can't send messages to yourself, so use `filehelper` instead.
228 |
229 | **Comments**
230 |
231 | If you have any problems or suggestions, you can talk to me in this `issue `__
232 |
233 | Or on `gitter `__.
234 |
235 | .. |QRCodeOfRobot| image:: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/200/
236 | .. |Python2| image:: https://img.shields.io/badge/python-2.7-ff69b4.svg
237 | .. |Python3| image:: https://img.shields.io/badge/python-3.5-red.svg
238 |
--------------------------------------------------------------------------------
/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 | * isAt: 判断是否@本号
290 | * ActualNickName: 实际NickName
291 | * Content: 实际Content
292 |
293 | ### 红包消息
294 |
295 | ```
296 | MsgType: 49
297 | AppMsgType: 2001
298 | FromUserName: 发送方ID
299 | ToUserName: 接收方ID
300 | Content: 未知
301 | ```
302 |
303 | ### 系统消息
304 |
305 | ```
306 | MsgType: 10000
307 | FromUserName: 发送方ID
308 | ToUserName: 自己ID
309 | Content:
310 | "你已添加了 xxx ,现在可以开始聊天了。"
311 | "如果陌生人主动添加你为朋友,请谨慎核实对方身份。"
312 | "收到红包,请在手机上查看"
313 | ```
314 |
--------------------------------------------------------------------------------
/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 enbetter 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 | ### Message register of various types
63 |
64 | The following is a demo of how itchat is configured to fetch and reply daily information.
65 |
66 | ```python
67 | #coding=utf8
68 | import itchat, time
69 | from itchat.content import *
70 |
71 | @itchat.msg_register([TEXT, MAP, CARD, NOTE, SHARING])
72 | def text_reply(msg):
73 | itchat.send('%s: %s' % (msg['Type'], msg['Text']), msg['FromUserName'])
74 |
75 | @itchat.msg_register([PICTURE, RECORDING, ATTACHMENT, VIDEO])
76 | def download_files(msg):
77 | msg['Text'](msg['FileName'])
78 | return '@%s@%s' % ({'Picture': 'img', 'Video': 'vid'}.get(msg['Type'], 'fil'), msg['FileName'])
79 |
80 | @itchat.msg_register(FRIENDS)
81 | def add_friend(msg):
82 | itchat.add_friend(**msg['Text']) # 该操作会自动将新好友的消息录入,不需要重载通讯录
83 | itchat.send_msg('Nice to meet you!', msg['RecommendInfo']['UserName'])
84 |
85 | @itchat.msg_register(TEXT, isGroupChat=True)
86 | def text_reply(msg):
87 | if msg['isAt']:
88 | itchat.send(u'@%s\u2005I received: %s' % (msg['ActualNickName'], msg['Content']), msg['FromUserName'])
89 |
90 | itchat.auto_login(True)
91 | itchat.run()
92 | ```
93 |
94 | ### Command line QR Code
95 |
96 | You can access the QR Code in command line through using this command:
97 |
98 | ```python
99 | itchat.auto_login(enableCmdQR=True)
100 | ```
101 |
102 | Because of width of some character differs from systems, you may adjust the enableCmdQR to fix the problem.
103 |
104 | ```python
105 | # for some linux system, width of block character is one instead of two, so enableCmdQR should be 2
106 | itchat.auto_login(enableCmdQR=2)
107 | ```
108 |
109 | Default background color of command line is dark (black), if it's not, you may set enableCmdQR to be negative:
110 |
111 | ```python
112 | itchat.auto_login(enableCmdQR=-1)
113 | ```
114 |
115 | ### Hot reload
116 |
117 | By using the following command, you may reload the program without re-scan QRCode in some time.
118 |
119 | ```python
120 | itchat.auto_login(hotReload=True)
121 | ```
122 |
123 | ### User search
124 |
125 | By using `search_friends`, you have four ways to search a user:
126 | 1. Get your own user information
127 | 2. Get user information through `UserName`
128 | 3. Get user information whose remark name or wechat account or nickname matches name key of the function
129 | 4. Get user information whose remark name, wechat account and nickname match what are given to the function
130 |
131 | Way 3, 4 can be used together, the following is the demo program:
132 |
133 | ```python
134 | # get your own user information
135 | itchat.search_friends()
136 | # get user information of specific username
137 | itchat.search_friends(userName='@abcdefg1234567')
138 | # get user information of function 3
139 | itchat.search_friends(name='littlecodersh')
140 | # get user information of function 4
141 | itchat.search_friends(wechatAccount='littlecodersh')
142 | # combination of way 3, 4
143 | itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh')
144 | ```
145 |
146 | There are detailed information about searching and getting of massive platforms and chatrooms in document.
147 |
148 | ### Download and send attachments
149 |
150 | The attachment download function of itchat is in Text key of msg
151 |
152 | Name of the file (default name of picture) is in FileName key of msg
153 |
154 | Download function accept one location value (include the file name) and store attachment accordingly.
155 |
156 | ```python
157 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
158 | def download_files(msg):
159 | msg['Text'](msg['FileName'])
160 | itchat.send('@%s@%s'%('img' if msg['Type'] == 'Picture' else 'fil', msg['FileName']), msg['FromUserName'])
161 | return '%s received'%msg['Type']
162 | ```
163 |
164 | If you don't want a local copy of the picture, you may pass nothing to the function to get a binary string.
165 |
166 | ```python
167 | @itchat.msg_register(['Picture', 'Recording', 'Attachment', 'Video'])
168 | def download_files(msg):
169 | with open(msg['FileName'], 'wb') as f:
170 | f.write(msg['Text']())
171 | ```
172 |
173 | ### Multi instance
174 |
175 | You may use the following commands to open multi instance.
176 |
177 | ```python
178 | import itchat
179 |
180 | newInstance = itchat.new_instance()
181 | newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl')
182 |
183 | @newInstance.msg_register(TEXT)
184 | def reply(msg):
185 | return msg['Text']
186 |
187 | newInstance.run()
188 | ```
189 |
190 | ### Set callback after login and logout
191 |
192 | Callback of login and logout are set through `loginCallback` and `exitCallback`.
193 |
194 | ```python
195 | import time
196 |
197 | import itchat
198 |
199 | def lc():
200 | print('finish login')
201 | def ec():
202 | print('exit')
203 |
204 | itchat.auto_login(loginCallback=lc, exitCallback=ec)
205 | time.sleep(3)
206 | itchat.logout()
207 | ```
208 |
209 | If loginCallback is not set, qr picture will be deleted and cmd will be cleared.
210 |
211 | If you exit through phone, exitCallback will also be called.
212 |
213 | ## FAQ
214 |
215 | Q: Why I can't upload files whose name is not purely english?
216 |
217 | A: This is caused because of the encoding of `requests`, you may fix it by placing [fields.py][fields.py-2](py3 version is [here][fields.py-3]) in packages/urllib3 of requests.
218 |
219 | Q: Why I still can't show QRCode with command line after I set enableCmdQr key to True in itchat.auto_login()?
220 |
221 | A: That's because you need to install optional site-package pillow, try this script: pip install pillow
222 |
223 | Q: How to use this package to use my wechat as an monitor?
224 |
225 | A: There are two ways: communicate with your own account or with filehelper.
226 |
227 | Q: Why sometimes I can't send messages?
228 |
229 | A: Some account simply can't send messages to yourself, so use `filehelper` instead.
230 |
231 | ## Author
232 |
233 | [LittleCoder][littlecodersh]: Structure and py2 py3 version
234 |
235 | [tempdban][tempdban]: Structure and daily maintainance
236 |
237 | [Chyroc][Chyroc]: first py3 version
238 |
239 | ## See also
240 |
241 | [liuwons/wxBot][liuwons-wxBot]: A wechat robot similiar to the robot branch
242 |
243 | [zixia/wechaty][zixia-wechaty]: wechat for bot in Javascript(ES6), Personal Account Robot Framework/Library
244 |
245 | [sjdy521/Mojo-Weixin][Mojo-Weixin]: wechat web api in Perl, available with HTTP requests
246 |
247 | ## Comments
248 |
249 | If you have any problems or suggestions, feel free to put it up in this [Issue][issue#1].
250 |
251 | Or you may also use [![Gitter][gitter-picture]][gitter]
252 |
253 | [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg
254 | [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge
255 | [py27]: https://img.shields.io/badge/python-2.7-ff69b4.svg
256 | [py35]: https://img.shields.io/badge/python-3.5-red.svg
257 | [chinese-version]: https://github.com/littlecodersh/ItChat/blob/master/README.md
258 | [document]: https://itchat.readthedocs.org/zh/latest/
259 | [robot-source-code]: https://gist.github.com/littlecodersh/ec8ddab12364323c97d4e36459174f0d
260 | [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/
261 | [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/
262 | [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/
263 | [fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705
264 | [fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc
265 | [littlecodersh]: https://github.com/littlecodersh
266 | [tempdban]: https://github.com/tempdban
267 | [Chyroc]: https://github.com/Chyroc
268 | [liuwons-wxBot]: https://github.com/liuwons/wxBot
269 | [zixia-wechaty]: https://github.com/zixia/wechaty
270 | [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin
271 | [issue#1]: https://github.com/littlecodersh/ItChat/issues/1
272 |
--------------------------------------------------------------------------------
/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 | * Windows 8.1
39 | * Python 2.7.11 (安装Image, requests)
40 | * Wireshark 2.0.2
41 | * 微信版本6.3.15
42 |
43 | ### Wireshark配置
44 |
45 | Wireshark是常见的抓包软件,这里通过一些配置抓取微信网页端的流量。
46 |
47 | 由于微信网页端使用https,需要特殊的配置才能看到有意义的内容,具体的配置见[这里](http://jingyan.baidu.com/article/20b68a88b2af7f796cec62b3.html)。
48 |
49 | 配置完成以后开始抓包,载入`https://www.baidu.com`后若能看到http请求则配置成功。
50 |
51 | ## 分析并模拟扫码,并获取登录状态
52 |
53 | 微信网页端登陆分为很多步,这里以第一步扫码为例讲解如何从抓包开始完成模拟。
54 |
55 | ### 分析过程
56 |
57 | 在抓包以前,我们需要先想清楚这是一个什么样的过程。
58 |
59 | 我们都登录过网页端微信,没有的话可以现在做一个尝试:[微信网页端](https://wx.qq.com)。
60 |
61 | 这个过程简单而言可以分为如下几步:
62 |
63 | 1. 向服务器提供一些用于获取二维码的数据
64 | 1. 服务器返回二维码
65 | 1. 向服务器询问二维码扫描状态
66 | 1. 服务器返回扫描状态
67 |
68 | 有了这些概念以后就可以开始将这四步和包对应起来。
69 |
70 | ### 对应过程与实际的包
71 |
72 | 开启wireshark抓包后登陆网页端微信,完成扫码登陆,然后关闭wireshark抓包。
73 |
74 | 筛选http请求(就是菜单栏下面输入的那个http),可以看到这样的界面。
75 |
76 | 
77 |
78 | 这里需要讲的就是第一列“No.”列的数字就是后文说的几号包,例如第一行就是30号包。数据包的类型则在Info列中可以看到,是GET,POST或是别的请求。
79 |
80 | 那么我们可以开始分析抓到的包了,我们先粗略的浏览一下数据包。
81 |
82 | 第325号包引起了我的注意,因为登陆过程当中非常有特征的一个过程是二维码的获取,所以我们尝试打开这一数据包的图片的内容。
83 |
84 | 
85 |
86 | 325号包是由292号包的请求获取的,292号包又是一个普通的get请求,所以我们尝试直接在浏览器中访问这一网址。(访问自己抓到的网址)
87 |
88 | 具体的网址通过双击打开292号包即可找到。如需要可以点击[这里](http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FTutorial%2FQRCodeDetailPackage.png)看图。
89 |
90 | 我们发现直接在浏览器中获取了一张二维码,所以这很有可能就是上述一、二步的过程了。
91 |
92 | 那么我们是向服务器提供了哪些数据获取了二维码呢?
93 | * 每次我们登录的二维码会变化,且没有随二维码传回的标识,所以我们肯定提供了每次不同的信息
94 | * 网址中最后一部分看起来比较像标识:https://login.weixin.qq.com/qrcode/4ZtmDT6OPg==
95 | * 为了进一步验证猜想,再次抓包,发现类似292号包的请求url仅最后一部分存在区别
96 | * 所以我们提供了`4ZtmDT6Opg==`获取到了这一二维码。
97 |
98 | 那么这一标识是随机生成的还是服务器获取的呢?
99 | * 从最近的包开始分析服务器传回的数据(Source是服务器地址的数据),发现就在上一行,286号包有我们感兴趣的数据。
100 | * 打开这个包,可以看到其返回的数据为`window.QRLogin.code = 200; window.QRLogin.uuid = "4ZtmDT6OPg==";`(见下方截图)
101 | * 显然导致服务器返回这一请求的284号包就是我们获取标识(下称uuid)所需要伪造的包。
102 |
103 | 
104 |
105 | 那么284号包需要传递给服务器哪些数据?
106 | * 这是一个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`。
107 | * 可以发现需要给出五个量`appid, redirect_uri, fun, lang, _`。
108 | * 其中除了appid其余都显然是固定的量(_的格式显然为时间戳)。
109 | * 然而搜索284号包之前的包也没有发现这一数值的来源,所以暂且认为其也是固定的量,模拟时如果出现问题再做尝试。
110 |
111 | 到了这里,1,2步的过程我们已经能够对应上相应的包了。
112 |
113 | 3,4部的最显著特征是在扫描成功以后会获取扫描用的微信号的头像。
114 |
115 | 我们还是首先大致的浏览一下服务器返回的数据包,试图找到包含图片的数据包。
116 | * 从325号包(微信头像肯定在二维码之后获取)开始浏览。
117 | * 我们发现338号包中包含一个base64加密的图片,[解压](http://www.vgot.net/test/image2base64.php?)后可以看到自己的头像。
118 | * 所以这个数据包就是服务器返回的扫描成功的数据包了,而前面那部分`window.code=201`显然就是表示状态的代码。(见下方截图)
119 | * 经过尝试与再次抓包,我们理解状态码的涵义:`200:登陆成功 201:扫描成功 408:图片过期`
120 | * 那么第四部我们已经能够完全的理解
121 |
122 | 
123 |
124 | 我们很容易的找到了在登录过程当中不断出现的请求,那么要怎么模拟呢?
125 | * 首先这是一个简单的get请求,url为:https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=4ZtmDT6OPg==&tip=1&r=-2026440414&_=1453725386009
126 | * 可以发现需要给出五个量`loginicon, uuid, tip, r, _`
127 | * 通过多次抓包发现除了r以外都可以找到简单的规律,那么r的规律等待模拟时再尝试处理
128 |
129 | 至此你应该已经能将四个过程全部与具体的数据包对应。为了避免有遗漏的过程,我们将没有使用到的与服务器交互的数据包标识出来(右键Mark)。经过简单的浏览,认为其中并没有必须的数据包交互。但值得注意的是,如果之后模拟数据包没有问题却无法登陆的话应当再回到这些数据包中搜寻。
130 |
131 | 这里做一个简单的小结,这一部分简单的介绍了分析数据包的基本思路,以及一些小的技巧。当然这些仅供参考,在具体的抓包中完全可以根据具体的交互过程自由发挥。而目前留下来的问题有:第一步时的appid与第三步时的r,留待模拟时在做研究。
132 |
133 | ### 使用Python模拟扫码
134 |
135 | 这一部分我们使用python的requests模块,可以通过`pip install requests`安装。
136 |
137 | 我们先来简单的讲述一下这个包。
138 | ```python
139 | import requests
140 | # 新建一个session对象(就像开了一个浏览器一样)
141 | session = requests.Session()
142 | # 使用get方法获取https://www.baidu.com/s?wd=python
143 | url = 'https://www.baidu.com/s'
144 | params = { 'wd': 'python', }
145 | r = session.get(url = url, params = params)
146 | with open('baidu.htm') as f: f.write(r.content) # 存入文件,可以使用浏览器尝试打开
147 | # 举例使用post方法
148 | import json
149 | url = 'https://www.baidu.com'
150 | data = { 'wd': 'python', }
151 | r = session.get(url = url, data = json.dumps(data))
152 | with open('baidu.htm') as f: f.write(r.content)
153 | # 以上代码与下面的代码不连续
154 | ```
155 |
156 | 如果想要更多的了解这个包,可以浏览[requests快速入门](http://blog.csdn.net/iloveyin/article/details/21444613)。
157 |
158 | 你可以尝试获取一个你熟悉的网站来测试使用requests,在测试时可以打开抓包,查看你发送的数据包与想要发送的数据包是否一样。
159 |
160 | 那么我们开始模拟第一、二个过程,向服务器提供一些用于获取二维码的数据,服务器返回二维码。
161 | * 向服务器提交284,292号包
162 | * 从服务器返回数据中提取出uuid与二维码图片
163 |
164 | **284号包**
165 |
166 | 
167 |
168 | 我们需要模拟的地址为: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 ,所以我们模拟的代码如下:
169 |
170 | ```python
171 | #coding=utf8
172 | import time, requests
173 | session = requests.Session()
174 | url = 'https://login.weixin.qq.com/jslogin'
175 | params = {
176 | 'appid': 'wx782c26e4c19acffb',
177 | 'redirect_uri': 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage',
178 | 'fun': 'new',
179 | 'lang': 'en_US',
180 | '_': int(time.time()),
181 | }
182 | r = session.get(url, params = params)
183 | print('Content: %s'%r.text)
184 | ```
185 |
186 | 当然,将模拟的地址全部写在url里面效果完全一样。
187 |
188 | 值得一提的是requests会帮我们自动urlencode,如果不需要urlencode(/变为了%2F)可以将所有内容都写在url里面。
189 |
190 | **提取出uuid**
191 |
192 | 这里使用re,如果不了解正则表达式的话可以直接拿来用,毕竟和这一个教程并不相关。
193 |
194 | ```python
195 | # 上接上一段程序
196 | import re
197 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";'
198 | # 我们可以看到返回的量是上述的格式,括号内的内容被提取了出来
199 | data = re.search(regx, r.text)
200 | if data and data.group(1) == '200': uuid = data.group(2)
201 | print('uuid: %s'%uuid)
202 | ```
203 |
204 | 如果没能成功获取到uuid可以尝试再运行一次。
205 |
206 | **292号包**
207 |
208 | 
209 |
210 | 我们需要模拟的url为:https://login.weixin.qq.com/qrcode/4ZtmDT6OPg== ,所以我们模拟的代码如下:
211 |
212 | ```python
213 | # 上接上一段程序
214 | url = 'https://login.weixin.qq.com/qrcode/' + uuid
215 | r = session.get(url, stream = True)
216 | with open('QRCode.jpg', 'wb') as f: f.write(r.content)
217 | # 现在你可以在你存储代码的位置发现一张存下来的图片,用下面的代码打开它
218 | import platform, os, subprocess
219 | if platform.system() == 'Darwin':
220 | subprocess.call(['open', 'QRCode.jpg'])
221 | elif platform.system() == 'Linux':
222 | subprocess.call(['xdg-open', 'QRCode.jpg'])
223 | else:
224 | os.startfile('QR.jpg')
225 | ```
226 |
227 | 由于我们需要获取图像,所以需要以二进制数据流的形式获取服务器返回的数据包,所以增加`stream = True`。
228 |
229 | 而将二进制数据流写入也需要在打开文件时设定二进制写入,即`open('QRCode.jpg', 'wb')`。
230 |
231 | 当然,如果获取失败可以再运行一次。
232 |
233 | 同理的三、四步也可以按照这个方法写出,这里就不再赘述,只给出代码。
234 |
235 | 而经过测试我们发现,第一步时的appid实际是一个固定的量,第三步时的r甚至不输入也可以登录。
236 |
237 | ```python
238 | # 上接上一段代码
239 | import time
240 |
241 | while 1:
242 | url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login'
243 | # 这里演示一下不使用自带的urlencode
244 | params = 'tip=1&uuid=%s&_=%s'%(uuid, int(time.time()))
245 | r = session.get(url, params = params)
246 | regx = r'window.code=(\d+)'
247 | data = re.search(regx, r.text)
248 | if not data: continue
249 | if data.group(1) == '200':
250 | # 下面一段是为了之后获取登录信息做准备
251 | uriRegex = r'window.redirect_uri="(\S+)";'
252 | redirectUri = re.search(uriRegex, r.text).group(1)
253 | r = session.get(redirectUri, allow_redirects=False)
254 | redirectUri = redirectUri[:redirectUri.rfind('/')]
255 | baseRequestText = r.text
256 | break
257 | elif data.group(1) == '201':
258 | print('You have scanned the QRCode')
259 | time.sleep(1)
260 | elif data.group(1) == '408':
261 | raise Exception('QRCode should be renewed')
262 | print('Login successfully')
263 | ```
264 |
265 | 当你看到**Login successfully**时,说明至此我们已经成功从零开始,通过抓包分析,用python成功模拟了python登陆。
266 |
267 | 不过是不是看上去没有什么反馈呢?那是因为我们还没有模拟会产生反馈的包,但其实差的只是研究发文字、发图片什么的包了。
268 |
269 | 为了体现我们已经登陆了,加上后面这段代码就可以看到登陆的账号信息:
270 |
271 | ```python
272 | # 上接上一段代码
273 | import xml.dom.minidom
274 | def get_login_info(s):
275 | baseRequest = {}
276 | for node in xml.dom.minidom.parseString(s).documentElement.childNodes:
277 | if node.nodeName == 'skey':
278 | baseRequest['Skey'] = node.childNodes[0].data.encode('utf8')
279 | elif node.nodeName == 'wxsid':
280 | baseRequest['Sid'] = node.childNodes[0].data.encode('utf8')
281 | elif node.nodeName == 'wxuin':
282 | baseRequest['Uin'] = node.childNodes[0].data.encode('utf8')
283 | elif node.nodeName == 'pass_ticket':
284 | baseRequest['DeviceID'] = node.childNodes[0].data.encode('utf8')
285 | return baseRequest
286 | baseRequest = get_login_info(baseRequestText)
287 |
288 | url = '%s/webwxinit?r=%s' % (redirectUri, int(time.time()))
289 | data = {
290 | 'BaseRequest': baseRequest,
291 | }
292 | headers = { 'ContentType': 'application/json; charset=UTF-8' }
293 | r = session.post(url, data = json.dumps(data), headers = headers)
294 | dic = json.loads(r.content.decode('utf-8', 'replace'))
295 |
296 | print('Log in as %s'%dic['User']['NickName'])
297 | ```
298 |
299 | 这里做一个简单的小结:
300 | * 首先需要用python初始化一个session,否则登录过程的存储将会比较麻烦。
301 | * 模拟数据包的时候首先区分get与post请求,对应session的get与post方法。
302 | * get的数据为url后半部分的内容,post是数据包最后一部分的内容。
303 | * get方法中传入数据的标示为params, post方法中传入数据的标示为data。
304 | * session的get,post方法返回一个量,可以通过r.text自动编码显示。
305 | * 存储图片有特殊的方式与配置。
306 |
307 | ## 小结
308 |
309 | 到现在为止我展示了一个完整的抓包、分析、模拟的过程完成了模拟登陆,其他一些事情其实也都是类似的过程,想清楚每一步要做些什么即可。
310 |
311 | 这里用到的软件都只介绍了最简单的一些方法,进一步的内容这里给出一些建议:
312 | * wireshark可以直接浏览官方文档,有空可以做一个了解。
313 | * requests包的使用通过搜索引擎即可,特殊的功能建议直接阅读源码。
314 |
315 | 那么做一个小练习好了,测试一下学到的东西:读取命令行的输入并发送给自己。(这部分的源码放在了文末)
316 |
317 | ## 具体运用时可能遇到的难点
318 |
319 | ### 命令行登录一段时间后无法与服务器正常交互
320 |
321 | 这是因为微信网页端存在心跳机制,一段时间不交互将会断开连接。
322 |
323 | 另外,每次获取数据时(webwxsync)记得更新SyncKey。
324 |
325 | ### 某个特定请求不知道如何模拟
326 |
327 | 在项目中已经模拟好了几乎所有的请求,你可以通过参考我的方法与数据包。
328 |
329 | 如果之后微信网页版出现更新我会在本项目中及时更新。
330 |
331 | 项目中的微信网页端接口见[这里](https://github.com/littlecodersh/ItChat/blob/master/itchat/client.py)
332 |
333 | ### 无法上传中文文件名的文件与图片
334 |
335 | 这是因为使用requests包会自动将中文文件名编码为服务器端无法识别的格式,所以需要修改requests包或者使用别的方法上传文件。
336 |
337 | 最简单的方法即将requests包的packages/urlib3中的fields.py中的`format_header_param`方法改为如下内容:
338 |
339 | ```python
340 | def format_header_param(name, value):
341 | if not any(ch in value for ch in '"\\\r\n'):
342 | result = '%s="%s"' % (name, value)
343 | try:
344 | result.encode('ascii')
345 | except UnicodeEncodeError:
346 | pass
347 | else:
348 | return result
349 | if not six.PY3: # Python 2:
350 | value = value.encode('utf-8')
351 | value = email.utils.encode_rfc2231(value, 'utf-8')
352 | value = '%s="%s"' % (name, value.decode('utf8'))
353 | return value
354 | ```
355 |
356 | ### 登录时出现不安全的提示
357 |
358 | 建议更新Python版本至2.7.11
359 |
360 | ## 小练习答案
361 |
362 | 源码可在该地址下载:[这里](https://github.com/littlecodersh/EasierLife/blob/master/Scripts/SendToMyself.py)
363 |
364 | ## 结束语
365 |
366 | 希望读完这篇文章能对你有帮助,有什么不足之处万望指正(鞠躬)。
367 |
368 | 有什么想法或者想要关注我的更新,欢迎来[**Github**](https://github.com/littlecodersh/ItChat)上***Star***或者***Fork***。
369 |
370 | 160318
371 |
372 | LittleCoder
373 |
374 | EOF
375 |
--------------------------------------------------------------------------------
/itchat/components/login.py:
--------------------------------------------------------------------------------
1 | import os, sys, time, re, io
2 | import threading
3 | import json, xml.dom.minidom
4 | import copy, pickle, random
5 | import traceback, logging
6 |
7 | import requests
8 |
9 | from .. import config, utils
10 | from ..returnvalues import ReturnValue
11 | from .contact import update_local_chatrooms, update_local_friends
12 | from .messages import produce_msg
13 |
14 | logger = logging.getLogger('itchat')
15 |
16 | def load_login(core):
17 | core.login = login
18 | core.get_QRuuid = get_QRuuid
19 | core.get_QR = get_QR
20 | core.check_login = check_login
21 | core.web_init = web_init
22 | core.show_mobile_login = show_mobile_login
23 | core.start_receiving = start_receiving
24 | core.get_msg = get_msg
25 | core.logout = logout
26 |
27 | def login(self, enableCmdQR=False, picDir=None, qrCallback=None,
28 | loginCallback=None, exitCallback=None):
29 | if self.alive:
30 | logger.warning('itchat has already logged in.')
31 | return
32 | while 1:
33 | for getCount in range(10):
34 | logger.info('Getting uuid of QR code.')
35 | while not self.get_QRuuid(): time.sleep(1)
36 | logger.info('Downloading QR code.')
37 | qrStorage = self.get_QR(enableCmdQR=enableCmdQR,
38 | picDir=picDir, qrCallback=qrCallback)
39 | if qrStorage:
40 | break
41 | elif 9 == getCount:
42 | logger.info('Failed to get QR code, please restart the program.')
43 | sys.exit()
44 | logger.info('Please scan the QR code to log in.')
45 | isLoggedIn = False
46 | while not isLoggedIn:
47 | status = self.check_login()
48 | if hasattr(qrCallback, '__call__'):
49 | qrCallback(uuid=self.uuid, status=status, qrcode=qrStorage.getvalue())
50 | if status == '200':
51 | isLoggedIn = True
52 | elif status == '201':
53 | if isLoggedIn is not None:
54 | logger.info('Please press confirm on your phone.')
55 | isLoggedIn = None
56 | elif status != '408':
57 | break
58 | if isLoggedIn: break
59 | logger.info('Log in time out, reloading QR code')
60 | self.web_init()
61 | self.show_mobile_login()
62 | self.get_contact(True)
63 | if hasattr(loginCallback, '__call__'):
64 | r = loginCallback()
65 | else:
66 | utils.clear_screen()
67 | if os.path.exists(picDir or config.DEFAULT_QR):
68 | os.remove(picDir or config.DEFAULT_QR)
69 | logger.info('Login successfully as %s' % self.storageClass.nickName)
70 | self.start_receiving(exitCallback)
71 |
72 | def get_QRuuid(self):
73 | url = '%s/jslogin' % config.BASE_URL
74 | params = {
75 | 'appid' : 'wx782c26e4c19acffb',
76 | 'fun' : 'new', }
77 | headers = { 'User-Agent' : config.USER_AGENT }
78 | r = self.s.get(url, params=params, headers=headers)
79 | regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)";'
80 | data = re.search(regx, r.text)
81 | if data and data.group(1) == '200':
82 | self.uuid = data.group(2)
83 | return self.uuid
84 |
85 | def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None):
86 | uuid = uuid or self.uuid
87 | picDir = picDir or config.DEFAULT_QR
88 | url = '%s/qrcode/%s' % (config.BASE_URL, uuid)
89 | headers = { 'User-Agent' : config.USER_AGENT }
90 | try:
91 | r = self.s.get(url, stream=True, headers=headers)
92 | except:
93 | return False
94 | qrStorage = io.BytesIO(r.content)
95 | if hasattr(qrCallback, '__call__'):
96 | qrCallback(uuid=uuid, status='0', qrcode=qrStorage.getvalue())
97 | else:
98 | with open(picDir, 'wb') as f: f.write(r.content)
99 | if enableCmdQR:
100 | utils.print_cmd_qr(picDir, enableCmdQR=enableCmdQR)
101 | else:
102 | utils.print_qr(picDir)
103 | return qrStorage
104 |
105 | def check_login(self, uuid=None):
106 | uuid = uuid or self.uuid
107 | url = '%s/cgi-bin/mmwebwx-bin/login' % config.BASE_URL
108 | localTime = int(time.time())
109 | params = 'loginicon=true&uuid=%s&tip=0&r=%s&_=%s' % (
110 | uuid, localTime / 1579, localTime)
111 | headers = { 'User-Agent' : config.USER_AGENT }
112 | r = self.s.get(url, params=params, headers=headers)
113 | regx = r'window.code=(\d+)'
114 | data = re.search(regx, r.text)
115 | if data and data.group(1) == '200':
116 | process_login_info(self, r.text)
117 | return '200'
118 | elif data:
119 | return data.group(1)
120 | else:
121 | return '400'
122 |
123 | def process_login_info(core, loginContent):
124 | ''' when finish login (scanning qrcode)
125 | * syncUrl and fileUploadingUrl will be fetched
126 | * deviceid and msgid will be generated
127 | * skey, wxsid, wxuin, pass_ticket will be fetched
128 | '''
129 | regx = r'window.redirect_uri="(\S+)";'
130 | core.loginInfo['url'] = re.search(regx, loginContent).group(1)
131 | headers = { 'User-Agent' : config.USER_AGENT }
132 | r = core.s.get(core.loginInfo['url'], headers=headers, allow_redirects=False)
133 | core.loginInfo['url'] = core.loginInfo['url'][:core.loginInfo['url'].rfind('/')]
134 | for indexUrl, detailedUrl in (
135 | ("wx2.qq.com" , ("file.wx2.qq.com", "webpush.wx2.qq.com")),
136 | ("wx8.qq.com" , ("file.wx8.qq.com", "webpush.wx8.qq.com")),
137 | ("qq.com" , ("file.wx.qq.com", "webpush.wx.qq.com")),
138 | ("web2.wechat.com" , ("file.web2.wechat.com", "webpush.web2.wechat.com")),
139 | ("wechat.com" , ("file.web.wechat.com", "webpush.web.wechat.com"))):
140 | fileUrl, syncUrl = ['https://%s/cgi-bin/mmwebwx-bin' % url for url in detailedUrl]
141 | if indexUrl in core.loginInfo['url']:
142 | core.loginInfo['fileUrl'], core.loginInfo['syncUrl'] = \
143 | fileUrl, syncUrl
144 | break
145 | else:
146 | core.loginInfo['fileUrl'] = core.loginInfo['syncUrl'] = core.loginInfo['url']
147 | core.loginInfo['deviceid'] = 'e' + repr(random.random())[2:17]
148 | core.loginInfo['BaseRequest'] = {}
149 | for node in xml.dom.minidom.parseString(r.text).documentElement.childNodes:
150 | if node.nodeName == 'skey':
151 | core.loginInfo['skey'] = core.loginInfo['BaseRequest']['Skey'] = node.childNodes[0].data
152 | elif node.nodeName == 'wxsid':
153 | core.loginInfo['wxsid'] = core.loginInfo['BaseRequest']['Sid'] = node.childNodes[0].data
154 | elif node.nodeName == 'wxuin':
155 | core.loginInfo['wxuin'] = core.loginInfo['BaseRequest']['Uin'] = node.childNodes[0].data
156 | elif node.nodeName == 'pass_ticket':
157 | core.loginInfo['pass_ticket'] = core.loginInfo['BaseRequest']['DeviceID'] = node.childNodes[0].data
158 |
159 | def web_init(self):
160 | url = '%s/webwxinit?r=%s' % (self.loginInfo['url'], int(time.time()))
161 | data = { 'BaseRequest': self.loginInfo['BaseRequest'], }
162 | headers = {
163 | 'ContentType': 'application/json; charset=UTF-8',
164 | 'User-Agent' : config.USER_AGENT, }
165 | r = self.s.post(url, data=json.dumps(data), headers=headers)
166 | dic = json.loads(r.content.decode('utf-8', 'replace'))
167 | # deal with login info
168 | utils.emoji_formatter(dic['User'], 'NickName')
169 | self.loginInfo['InviteStartCount'] = int(dic['InviteStartCount'])
170 | self.loginInfo['User'] = utils.struct_friend_info(dic['User'])
171 | self.loginInfo['SyncKey'] = dic['SyncKey']
172 | self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
173 | for item in dic['SyncKey']['List']])
174 | self.storageClass.userName = dic['User']['UserName']
175 | self.storageClass.nickName = dic['User']['NickName']
176 | return dic
177 |
178 | def show_mobile_login(self):
179 | url = '%s/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (
180 | self.loginInfo['url'], self.loginInfo['pass_ticket'])
181 | data = {
182 | 'BaseRequest' : self.loginInfo['BaseRequest'],
183 | 'Code' : 3,
184 | 'FromUserName' : self.storageClass.userName,
185 | 'ToUserName' : self.storageClass.userName,
186 | 'ClientMsgId' : int(time.time()), }
187 | headers = {
188 | 'ContentType': 'application/json; charset=UTF-8',
189 | 'User-Agent' : config.USER_AGENT, }
190 | r = self.s.post(url, data=json.dumps(data), headers=headers)
191 | return ReturnValue(rawResponse=r)
192 |
193 | def start_receiving(self, exitCallback=None, getReceivingFnOnly=False):
194 | self.alive = True
195 | def maintain_loop():
196 | retryCount = 0
197 | while self.alive:
198 | try:
199 | i = sync_check(self)
200 | if i is None:
201 | self.alive = False
202 | elif i == '0':
203 | continue
204 | else:
205 | msgList, contactList = self.get_msg()
206 | if msgList:
207 | msgList = produce_msg(self, msgList)
208 | for msg in msgList: self.msgList.put(msg)
209 | if contactList:
210 | chatroomList, otherList = [], []
211 | for contact in contactList:
212 | if '@@' in contact['UserName']:
213 | chatroomList.append(contact)
214 | else:
215 | otherList.append(contact)
216 | chatroomMsg = update_local_chatrooms(self, chatroomList)
217 | self.msgList.put(chatroomMsg)
218 | update_local_friends(self, otherList)
219 | retryCount = 0
220 | except:
221 | retryCount += 1
222 | logger.error(traceback.format_exc())
223 | if self.receivingRetryCount < retryCount:
224 | self.alive = False
225 | else:
226 | time.sleep(1)
227 | self.logout()
228 | if hasattr(exitCallback, '__call__'):
229 | exitCallback()
230 | else:
231 | logger.info('LOG OUT!')
232 | if getReceivingFnOnly:
233 | return maintain_loop
234 | else:
235 | maintainThread = threading.Thread(target=maintain_loop)
236 | maintainThread.setDaemon(True)
237 | maintainThread.start()
238 |
239 | def sync_check(self):
240 | url = '%s/synccheck' % self.loginInfo.get('syncUrl', self.loginInfo['url'])
241 | params = {
242 | 'r' : int(time.time() * 1000),
243 | 'skey' : self.loginInfo['skey'],
244 | 'sid' : self.loginInfo['wxsid'],
245 | 'uin' : self.loginInfo['wxuin'],
246 | 'deviceid' : self.loginInfo['deviceid'],
247 | 'synckey' : self.loginInfo['synckey'],
248 | '_' : int(time.time() * 1000),}
249 | headers = { 'User-Agent' : config.USER_AGENT }
250 | r = self.s.get(url, params=params, headers=headers)
251 | regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}'
252 | pm = re.search(regx, r.text)
253 | if pm is None or pm.group(1) != '0':
254 | logger.debug('Unexpected sync check result: %s' % r.text)
255 | return None
256 | return pm.group(2)
257 |
258 | def get_msg(self):
259 | url = '%s/webwxsync?sid=%s&skey=%s&pass_ticket=%s' % (
260 | self.loginInfo['url'], self.loginInfo['wxsid'],
261 | self.loginInfo['skey'],self.loginInfo['pass_ticket'])
262 | data = {
263 | 'BaseRequest' : self.loginInfo['BaseRequest'],
264 | 'SyncKey' : self.loginInfo['SyncKey'],
265 | 'rr' : ~int(time.time()), }
266 | headers = {
267 | 'ContentType': 'application/json; charset=UTF-8',
268 | 'User-Agent' : config.USER_AGENT }
269 | r = self.s.post(url, data=json.dumps(data), headers=headers)
270 | dic = json.loads(r.content.decode('utf-8', 'replace'))
271 | if dic['BaseResponse']['Ret'] != 0: return None, None
272 | self.loginInfo['SyncKey'] = dic['SyncCheckKey']
273 | self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val'])
274 | for item in dic['SyncCheckKey']['List']])
275 | return dic['AddMsgList'], dic['ModContactList']
276 |
277 | def logout(self):
278 | if self.alive:
279 | url = '%s/webwxlogout' % self.loginInfo['url']
280 | params = {
281 | 'redirect' : 1,
282 | 'type' : 1,
283 | 'skey' : self.loginInfo['skey'], }
284 | headers = { 'User-Agent' : config.USER_AGENT }
285 | self.s.get(url, params=params, headers=headers)
286 | self.alive = False
287 | self.s.cookies.clear()
288 | del self.chatroomList[:]
289 | del self.memberList[:]
290 | del self.mpList[:]
291 | return ReturnValue({'BaseResponse': {
292 | 'ErrMsg': 'logout successfully.',
293 | 'Ret': 0, }})
294 |
--------------------------------------------------------------------------------
/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 util
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/components/messages.py:
--------------------------------------------------------------------------------
1 | import os, time, re, io
2 | import json
3 | import mimetypes, hashlib
4 | import traceback, logging
5 | from collections import OrderedDict
6 |
7 | import requests
8 |
9 | from .. import config, utils
10 | from ..returnvalues import ReturnValue
11 | from .contact import update_local_uin
12 |
13 | logger = logging.getLogger('itchat')
14 |
15 | def load_messages(core):
16 | core.send_raw_msg = send_raw_msg
17 | core.send_msg = send_msg
18 | core.upload_file = upload_file
19 | core.send_file = send_file
20 | core.send_image = send_image
21 | core.send_video = send_video
22 | core.send = send
23 |
24 | def get_download_fn(core, url, msgId):
25 | def download_fn(downloadDir=None):
26 | params = {
27 | 'msgid': msgId,
28 | 'skey': core.loginInfo['skey'],}
29 | headers = { 'User-Agent' : config.USER_AGENT }
30 | r = core.s.get(url, params=params, stream=True, headers = headers)
31 | tempStorage = io.BytesIO()
32 | for block in r.iter_content(1024):
33 | tempStorage.write(block)
34 | if downloadDir is None: return tempStorage.getvalue()
35 | with open(downloadDir, 'wb') as f: f.write(tempStorage.getvalue())
36 | return ReturnValue({'BaseResponse': {
37 | 'ErrMsg': 'Successfully downloaded',
38 | 'Ret': 0, }})
39 | return download_fn
40 |
41 | def produce_msg(core, msgList):
42 | ''' for messages types
43 | * 40 msg, 43 videochat, 50 VOIPMSG, 52 voipnotifymsg
44 | * 53 webwxvoipnotifymsg, 9999 sysnotice
45 | '''
46 | rl = []
47 | srl = [40, 43, 50, 52, 53, 9999]
48 | for m in msgList:
49 | if '@@' in m['FromUserName'] or '@@' in m['ToUserName']:
50 | produce_group_chat(core, m)
51 | else:
52 | utils.msg_formatter(m, 'Content')
53 | if m['MsgType'] == 1: # words
54 | if m['Url']:
55 | regx = r'(.+?\(.+?\))'
56 | data = re.search(regx, m['Content'])
57 | data = 'Map' if data is None else data.group(1)
58 | msg = {
59 | 'Type': 'Map',
60 | 'Text': data,}
61 | else:
62 | msg = {
63 | 'Type': 'Text',
64 | 'Text': m['Content'],}
65 | elif m['MsgType'] == 3 or m['MsgType'] == 47: # picture
66 | download_fn = get_download_fn(core,
67 | '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId'])
68 | msg = {
69 | 'Type' : 'Picture',
70 | 'FileName' : '%s.%s' % (time.strftime('%y%m%d-%H%M%S', time.localtime()),
71 | 'png' if m['MsgType'] == 3 else 'gif'),
72 | 'Text' : download_fn, }
73 | elif m['MsgType'] == 34: # voice
74 | download_fn = get_download_fn(core,
75 | '%s/webwxgetvoice' % core.loginInfo['url'], m['NewMsgId'])
76 | msg = {
77 | 'Type': 'Recording',
78 | 'FileName' : '%s.mp3' % time.strftime('%y%m%d-%H%M%S', time.localtime()),
79 | 'Text': download_fn,}
80 | elif m['MsgType'] == 37: # friends
81 | msg = {
82 | 'Type': 'Friends',
83 | 'Text': {
84 | 'status' : m['Status'],
85 | 'userName' : m['RecommendInfo']['UserName'],
86 | 'verifyContent' : m['Ticket'],
87 | 'autoUpdate' : m['RecommendInfo'], }, }
88 | elif m['MsgType'] == 42: # name card
89 | msg = {
90 | 'Type': 'Card',
91 | 'Text': m['RecommendInfo'], }
92 | elif m['MsgType'] in (43, 62): # tiny video
93 | msgId = m['MsgId']
94 | def download_video(videoDir=None):
95 | url = '%s/webwxgetvideo' % core.loginInfo['url']
96 | params = {
97 | 'msgid': msgId,
98 | 'skey': core.loginInfo['skey'],}
99 | headers = {'Range': 'bytes=0-', 'User-Agent' : config.USER_AGENT }
100 | r = core.s.get(url, params=params, headers=headers, stream=True)
101 | tempStorage = io.BytesIO()
102 | for block in r.iter_content(1024):
103 | tempStorage.write(block)
104 | if videoDir is None: return tempStorage.getvalue()
105 | with open(videoDir, 'wb') as f: f.write(tempStorage.getvalue())
106 | return ReturnValue({'BaseResponse': {
107 | 'ErrMsg': 'Successfully downloaded',
108 | 'Ret': 0, }})
109 | msg = {
110 | 'Type': 'Video',
111 | 'FileName' : '%s.mp4' % time.strftime('%y%m%d-%H%M%S', time.localtime()),
112 | 'Text': download_video, }
113 | elif m['MsgType'] == 49: # sharing
114 | if m['AppMsgType'] == 6:
115 | rawMsg = m
116 | cookiesList = {name:data for name,data in core.s.cookies.items()}
117 | def download_atta(attaDir=None):
118 | url = core.loginInfo['fileUrl'] + '/webwxgetmedia'
119 | params = {
120 | 'sender': rawMsg['FromUserName'],
121 | 'mediaid': rawMsg['MediaId'],
122 | 'filename': rawMsg['FileName'],
123 | 'fromuser': core.loginInfo['wxuin'],
124 | 'pass_ticket': 'undefined',
125 | 'webwx_data_ticket': cookiesList['webwx_data_ticket'],}
126 | headers = { 'User-Agent' : config.USER_AGENT }
127 | r = core.s.get(url, params=params, stream=True, headers=headers)
128 | tempStorage = io.BytesIO()
129 | for block in r.iter_content(1024):
130 | tempStorage.write(block)
131 | if attaDir is None: return tempStorage.getvalue()
132 | with open(attaDir, 'wb') as f: f.write(tempStorage.getvalue())
133 | return ReturnValue({'BaseResponse': {
134 | 'ErrMsg': 'Successfully downloaded',
135 | 'Ret': 0, }})
136 | msg = {
137 | 'Type': 'Attachment',
138 | 'Text': download_atta, }
139 | elif m['AppMsgType'] == 8:
140 | download_fn = get_download_fn(core,
141 | '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId'])
142 | msg = {
143 | 'Type' : 'Picture',
144 | 'FileName' : '%s.gif' % (
145 | time.strftime('%y%m%d-%H%M%S', time.localtime())),
146 | 'Text' : download_fn, }
147 | elif m['AppMsgType'] == 17:
148 | msg = {
149 | 'Type': 'Note',
150 | 'Text': m['FileName'], }
151 | elif m['AppMsgType'] == 2000:
152 | regx = r'\[CDATA\[(.+?)\][\s\S]+?\[CDATA\[(.+?)\]'
153 | data = re.search(regx, m['Content'])
154 | if data:
155 | data = data.group(2).split(u'\u3002')[0]
156 | else:
157 | data = 'You may found detailed info in Content key.'
158 | msg = {
159 | 'Type': 'Note',
160 | 'Text': data, }
161 | else:
162 | msg = {
163 | 'Type': 'Sharing',
164 | 'Text': m['FileName'], }
165 | elif m['MsgType'] == 51: # phone init
166 | msg = update_local_uin(core, m)
167 | elif m['MsgType'] == 10000:
168 | msg = {
169 | 'Type': 'Note',
170 | 'Text': m['Content'],}
171 | elif m['MsgType'] == 10002:
172 | regx = r'\[CDATA\[(.+?)\]\]'
173 | data = re.search(regx, m['Content'])
174 | data = 'System message' if data is None else data.group(1).replace('\\', '')
175 | msg = {
176 | 'Type': 'Note',
177 | 'Text': data, }
178 | elif m['MsgType'] in srl:
179 | msg = {
180 | 'Type': 'Useless',
181 | 'Text': 'UselessMsg', }
182 | else:
183 | logger.debug('Useless message received: %s\n%s' % (m['MsgType'], str(m)))
184 | msg = {
185 | 'Type': 'Useless',
186 | 'Text': 'UselessMsg', }
187 | m = dict(m, **msg)
188 | rl.append(m)
189 | return rl
190 |
191 | def produce_group_chat(core, msg):
192 | r = re.match('(@[0-9a-z]*?):
(.*)$', msg['Content'])
193 | if not r:
194 | utils.msg_formatter(msg, 'Content')
195 | return
196 | actualUserName, content = r.groups()
197 | chatroom = core.storageClass.search_chatrooms(userName=msg['FromUserName'])
198 | member = utils.search_dict_list((chatroom or {}).get(
199 | 'MemberList') or [], 'UserName', actualUserName)
200 | if member is None:
201 | chatroom = core.update_chatroom(msg['FromUserName'])
202 | member = utils.search_dict_list((chatroom or {}).get(
203 | 'MemberList') or [], 'UserName', actualUserName)
204 | msg['ActualUserName'] = actualUserName
205 | msg['ActualNickName'] = member['DisplayName'] or member['NickName']
206 | msg['Content'] = content
207 | utils.msg_formatter(msg, 'Content')
208 | atFlag = '@' + (chatroom['self']['DisplayName']
209 | or core.storageClass.nickName)
210 | msg['isAt'] = (
211 | (atFlag + (u'\u2005' if u'\u2005' in msg['Content'] else ' '))
212 | in msg['Content']
213 | or
214 | msg['Content'].endswith(atFlag))
215 |
216 | def send_raw_msg(self, msgType, content, toUserName):
217 | url = '%s/webwxsendmsg' % self.loginInfo['url']
218 | data = {
219 | 'BaseRequest': self.loginInfo['BaseRequest'],
220 | 'Msg': {
221 | 'Type': msgType,
222 | 'Content': content,
223 | 'FromUserName': self.storageClass.userName,
224 | 'ToUserName': (toUserName if toUserName else self.storageClass.userName),
225 | 'LocalID': int(time.time() * 1e4),
226 | 'ClientMsgId': int(time.time() * 1e4),
227 | },
228 | 'Scene': 0, }
229 | headers = { 'ContentType': 'application/json; charset=UTF-8', 'User-Agent' : config.USER_AGENT }
230 | r = self.s.post(url, headers=headers,
231 | data=json.dumps(data, ensure_ascii=False).encode('utf8'))
232 | return ReturnValue(rawResponse=r)
233 |
234 | def send_msg(self, msg='Test Message', toUserName=None):
235 | logger.debug('Request to send a text message to %s: %s' % (toUserName, msg))
236 | r = self.send_raw_msg(1, msg, toUserName)
237 | return r
238 |
239 | def upload_file(self, fileDir, isPicture=False, isVideo=False,
240 | toUserName='filehelper'):
241 | logger.debug('Request to upload a %s: %s' % (
242 | 'picture' if isPicture else 'video' if isVideo else 'file', fileDir))
243 | if not utils.check_file(fileDir):
244 | return ReturnValue({'BaseResponse': {
245 | 'ErrMsg': 'No file found in specific dir',
246 | 'Ret': -1002, }})
247 | fileSize = os.path.getsize(fileDir)
248 | fileSymbol = 'pic' if isPicture else 'video' if isVideo else'doc'
249 | with open(fileDir, 'rb') as f: fileMd5 = hashlib.md5(f.read()).hexdigest()
250 | file = open(fileDir, 'rb')
251 | chunks = int((fileSize - 1) / 524288) + 1
252 | clientMediaId = int(time.time() * 1e4)
253 | uploadMediaRequest = json.dumps(OrderedDict([
254 | ('UploadType', 2),
255 | ('BaseRequest', self.loginInfo['BaseRequest']),
256 | ('ClientMediaId', clientMediaId),
257 | ('TotalLen', fileSize),
258 | ('StartPos', 0),
259 | ('DataLen', fileSize),
260 | ('MediaType', 4),
261 | ('FromUserName', self.storageClass.userName),
262 | ('ToUserName', toUserName),
263 | ('FileMd5', fileMd5)]
264 | ), separators = (',', ':'))
265 | for chunk in range(chunks):
266 | r = upload_chunk_file(self, fileDir, fileSymbol, fileSize,
267 | file, chunk, chunks, uploadMediaRequest)
268 | file.close()
269 | return ReturnValue(rawResponse=r)
270 |
271 | def upload_chunk_file(core, fileDir, fileSymbol, fileSize,
272 | file, chunk, chunks, uploadMediaRequest):
273 | url = core.loginInfo.get('fileUrl', core.loginInfo['url']) + \
274 | '/webwxuploadmedia?f=json'
275 | # save it on server
276 | cookiesList = {name:data for name,data in core.s.cookies.items()}
277 | fileType = mimetypes.guess_type(fileDir)[0] or 'application/octet-stream'
278 | files = OrderedDict([
279 | ('id', (None, 'WU_FILE_0')),
280 | ('name', (None, os.path.basename(fileDir))),
281 | ('type', (None, fileType)),
282 | ('lastModifiedDate', (None, time.strftime('%a %b %d %Y %H:%M:%S GMT+0800 (CST)'))),
283 | ('size', (None, str(fileSize))),
284 | ('chunks', (None, None)),
285 | ('chunk', (None, None)),
286 | ('mediatype', (None, fileSymbol)),
287 | ('uploadmediarequest', (None, uploadMediaRequest)),
288 | ('webwx_data_ticket', (None, cookiesList['webwx_data_ticket'])),
289 | ('pass_ticket', (None, core.loginInfo['pass_ticket'])),
290 | ('filename' , (os.path.basename(fileDir), file.read(524288), 'application/octet-stream'))])
291 | if chunks == 1:
292 | del files['chunk']; del files['chunks']
293 | else:
294 | files['chunk'], files['chunks'] = (None, str(chunk)), (None, str(chunks))
295 | headers = { 'User-Agent' : config.USER_AGENT }
296 | return requests.post(url, files=files, headers=headers)
297 |
298 | def send_file(self, fileDir, toUserName=None, mediaId=None):
299 | logger.debug('Request to send a file(mediaId: %s) to %s: %s' % (
300 | mediaId, toUserName, fileDir))
301 | if toUserName is None: toUserName = self.storageClass.userName
302 | if mediaId is None:
303 | r = self.upload_file(fileDir)
304 | if r:
305 | mediaId = r['MediaId']
306 | else:
307 | return r
308 | url = '%s/webwxsendappmsg?fun=async&f=json' % self.loginInfo['url']
309 | data = {
310 | 'BaseRequest': self.loginInfo['BaseRequest'],
311 | 'Msg': {
312 | 'Type': 6,
313 | 'Content': ("%s"%os.path.basename(fileDir) +
314 | "6" +
315 | "%s%s"%(str(os.path.getsize(fileDir)), mediaId) +
316 | "%s"%os.path.splitext(fileDir)[1].replace('.','')),
317 | 'FromUserName': self.storageClass.userName,
318 | 'ToUserName': toUserName,
319 | 'LocalID': int(time.time() * 1e4),
320 | 'ClientMsgId': int(time.time() * 1e4), },
321 | 'Scene': 0, }
322 | headers = {
323 | 'User-Agent': config.USER_AGENT,
324 | 'Content-Type': 'application/json;charset=UTF-8', }
325 | r = self.s.post(url, headers=headers,
326 | data=json.dumps(data, ensure_ascii=False).encode('utf8'))
327 | return ReturnValue(rawResponse=r)
328 |
329 | def send_image(self, fileDir, toUserName=None, mediaId=None):
330 | logger.debug('Request to send a image(mediaId: %s) to %s: %s' % (
331 | mediaId, toUserName, fileDir))
332 | if toUserName is None: toUserName = self.storageClass.userName
333 | if mediaId is None:
334 | r = self.upload_file(fileDir, isPicture=not fileDir[-4:] == '.gif')
335 | if r:
336 | mediaId = r['MediaId']
337 | else:
338 | return r
339 | url = '%s/webwxsendmsgimg?fun=async&f=json' % self.loginInfo['url']
340 | data = {
341 | 'BaseRequest': self.loginInfo['BaseRequest'],
342 | 'Msg': {
343 | 'Type': 3,
344 | 'MediaId': mediaId,
345 | 'FromUserName': self.storageClass.userName,
346 | 'ToUserName': toUserName,
347 | 'LocalID': int(time.time() * 1e4),
348 | 'ClientMsgId': int(time.time() * 1e4), },
349 | 'Scene': 0, }
350 | if fileDir[-4:] == '.gif':
351 | url = '%s/webwxsendemoticon?fun=sys' % self.loginInfo['url']
352 | data['Msg']['Type'] = 47
353 | data['Msg']['EmojiFlag'] = 2
354 | headers = {
355 | 'User-Agent': config.USER_AGENT,
356 | 'Content-Type': 'application/json;charset=UTF-8', }
357 | r = self.s.post(url, headers=headers,
358 | data=json.dumps(data, ensure_ascii=False).encode('utf8'))
359 | return ReturnValue(rawResponse=r)
360 |
361 | def send_video(self, fileDir=None, toUserName=None, mediaId=None):
362 | logger.debug('Request to send a video(mediaId: %s) to %s: %s' % (
363 | mediaId, toUserName, fileDir))
364 | if toUserName is None: toUserName = self.storageClass.userName
365 | if mediaId is None:
366 | r = self.upload_file(fileDir, isVideo=True)
367 | if r:
368 | mediaId = r['MediaId']
369 | else:
370 | return r
371 | url = '%s/webwxsendvideomsg?fun=async&f=json&pass_ticket=%s' % (
372 | self.loginInfo['url'], self.loginInfo['pass_ticket'])
373 | data = {
374 | 'BaseRequest': self.loginInfo['BaseRequest'],
375 | 'Msg': {
376 | 'Type' : 43,
377 | 'MediaId' : mediaId,
378 | 'FromUserName' : self.storageClass.userName,
379 | 'ToUserName' : toUserName,
380 | 'LocalID' : int(time.time() * 1e4),
381 | 'ClientMsgId' : int(time.time() * 1e4), },
382 | 'Scene': 0, }
383 | headers = {
384 | 'User-Agent' : config.USER_AGENT,
385 | 'Content-Type': 'application/json;charset=UTF-8', }
386 | r = self.s.post(url, headers=headers,
387 | data=json.dumps(data, ensure_ascii=False).encode('utf8'))
388 | return ReturnValue(rawResponse=r)
389 |
390 | def send(self, msg, toUserName=None, mediaId=None):
391 | if not msg:
392 | r = ReturnValue({'BaseResponse': {
393 | 'ErrMsg': 'No message.',
394 | 'Ret': -1005, }})
395 | elif msg[:5] == '@fil@':
396 | if mediaId is None:
397 | r = self.send_file(msg[5:], toUserName)
398 | else:
399 | r = self.send_file(msg[5:], toUserName, mediaId)
400 | elif msg[:5] == '@img@':
401 | if mediaId is None:
402 | r = self.send_image(msg[5:], toUserName)
403 | else:
404 | r = self.send_image(msg[5:], toUserName, mediaId)
405 | elif msg[:5] == '@msg@':
406 | r = self.send_msg(msg[5:], toUserName)
407 | elif msg[:5] == '@vid@':
408 | if mediaId is None:
409 | r = self.send_video(msg[5:], toUserName)
410 | else:
411 | r = self.send_video(msg[5:], toUserName, mediaId)
412 | else:
413 | r = self.send_msg(msg, toUserName)
414 | return r
415 |
--------------------------------------------------------------------------------
/itchat/components/contact.py:
--------------------------------------------------------------------------------
1 | import os, time, re, io
2 | import json, copy
3 | import traceback, logging
4 |
5 | import requests
6 |
7 | from .. import config, utils
8 | from ..returnvalues import ReturnValue
9 |
10 | logger = logging.getLogger('itchat')
11 |
12 | def load_contact(core):
13 | core.update_chatroom = update_chatroom
14 | core.update_friend = update_friend
15 | core.get_contact = get_contact
16 | core.get_friends = get_friends
17 | core.get_chatrooms = get_chatrooms
18 | core.get_mps = get_mps
19 | core.set_alias = set_alias
20 | core.set_pinned = set_pinned
21 | core.add_friend = add_friend
22 | core.get_head_img = get_head_img
23 | core.create_chatroom = create_chatroom
24 | core.set_chatroom_name = set_chatroom_name
25 | core.delete_member_from_chatroom = delete_member_from_chatroom
26 | core.add_member_into_chatroom = add_member_into_chatroom
27 |
28 | def update_chatroom(self, userName, detailedMember=False):
29 | if not isinstance(userName, list):
30 | userName = [userName]
31 | url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
32 | self.loginInfo['url'], int(time.time()))
33 | headers = {
34 | 'ContentType': 'application/json; charset=UTF-8',
35 | 'User-Agent' : config.USER_AGENT }
36 | data = {
37 | 'BaseRequest': self.loginInfo['BaseRequest'],
38 | 'Count': len(userName),
39 | 'List': [{
40 | 'UserName': u,
41 | 'ChatRoomId': '', } for u in userName], }
42 | chatroomList = json.loads(self.s.post(url, data=json.dumps(data), headers=headers
43 | ).content.decode('utf8', 'replace')).get('ContactList')
44 | if not chatroomList:
45 | return ReturnValue({'BaseResponse': {
46 | 'ErrMsg': 'No chatroom found',
47 | 'Ret': -1001, }})
48 |
49 | if detailedMember:
50 | def get_detailed_member_info(encryChatroomId, memberList):
51 | url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
52 | self.loginInfo['url'], int(time.time()))
53 | headers = {
54 | 'ContentType': 'application/json; charset=UTF-8',
55 | 'User-Agent' : config.USER_AGENT, }
56 | data = {
57 | 'BaseRequest': self.loginInfo['BaseRequest'],
58 | 'Count': len(memberList),
59 | 'List': [{
60 | 'UserName': member['UserName'],
61 | 'EncryChatRoomId': encryChatroomId} \
62 | for member in memberList], }
63 | return json.loads(self.s.post(url, data=json.dumps(data), headers=headers
64 | ).content.decode('utf8', 'replace'))['ContactList']
65 | MAX_GET_NUMBER = 50
66 | for chatroom in chatroomList:
67 | totalMemberList = []
68 | for i in range(int(len(chatroom['MemberList']) / MAX_GET_NUMBER + 1)):
69 | memberList = chatroom['MemberList'][i*MAX_GET_NUMBER: (i+1)*MAX_GET_NUMBER]
70 | totalMemberList += get_detailed_member_info(chatroom['EncryChatRoomId'], memberList)
71 | chatroom['MemberList'] = totalMemberList
72 |
73 | update_local_chatrooms(self, chatroomList)
74 | r = [self.storageClass.search_chatrooms(userName=c['UserName'])
75 | for c in chatroomList]
76 | return r if 1 < len(r) else r[0]
77 |
78 | def update_friend(self, userName):
79 | if not isinstance(userName, list):
80 | userName = [userName]
81 | url = '%s/webwxbatchgetcontact?type=ex&r=%s' % (
82 | self.loginInfo['url'], int(time.time()))
83 | headers = {
84 | 'ContentType': 'application/json; charset=UTF-8',
85 | 'User-Agent' : config.USER_AGENT }
86 | data = {
87 | 'BaseRequest': self.loginInfo['BaseRequest'],
88 | 'Count': len(userName),
89 | 'List': [{
90 | 'UserName': u,
91 | 'EncryChatRoomId': '', } for u in userName], }
92 | friendList = json.loads(self.s.post(url, data=json.dumps(data), headers=headers
93 | ).content.decode('utf8', 'replace')).get('ContactList')
94 |
95 | update_local_friends(self, friendList)
96 | r = [self.storageClass.search_friends(userName=f['UserName'])
97 | for f in friendList]
98 | return r if len(r) != 1 else r[0]
99 |
100 | def update_info_dict(oldInfoDict, newInfoDict):
101 | '''
102 | only normal values will be updated here
103 | '''
104 | for k, v in newInfoDict.items():
105 | if any((isinstance(v, t) for t in (tuple, list, dict))):
106 | pass # these values will be updated somewhere else
107 | elif oldInfoDict.get(k) is None or v not in (None, '', '0', 0):
108 | oldInfoDict[k] = v
109 |
110 | def update_local_chatrooms(core, l):
111 | '''
112 | get a list of chatrooms for updating local chatrooms
113 | return a list of given chatrooms with updated info
114 | '''
115 | for chatroom in l:
116 | # format new chatrooms
117 | utils.emoji_formatter(chatroom, 'NickName')
118 | for member in chatroom['MemberList']:
119 | utils.emoji_formatter(member, 'NickName')
120 | utils.emoji_formatter(member, 'DisplayName')
121 | # update it to old chatrooms
122 | oldChatroom = utils.search_dict_list(
123 | core.chatroomList, 'UserName', chatroom['UserName'])
124 | if oldChatroom:
125 | update_info_dict(oldChatroom, chatroom)
126 | # - update other values
127 | memberList, oldMemberList = (c.get('MemberList', [])
128 | for c in (chatroom, oldChatroom))
129 | if memberList:
130 | for member in memberList:
131 | oldMember = utils.search_dict_list(
132 | oldMemberList, 'UserName', member['UserName'])
133 | if oldMember:
134 | update_info_dict(oldMember, member)
135 | else:
136 | oldMemberList.append(member)
137 | else:
138 | oldChatroom = chatroom
139 | core.chatroomList.append(chatroom)
140 | # delete useless members
141 | if len(chatroom['MemberList']) != len(oldChatroom['MemberList']) and \
142 | chatroom['MemberList']:
143 | existsUserNames = [member['UserName'] for member in chatroom['MemberList']]
144 | delList = []
145 | for i, member in enumerate(oldChatroom['MemberList']):
146 | if member['UserName'] not in existsUserNames: delList.append(i)
147 | delList.sort(reverse=True)
148 | for i in delList: del oldChatroom['MemberList'][i]
149 | # - update OwnerUin
150 | if oldChatroom.get('ChatRoomOwner') and oldChatroom.get('MemberList'):
151 | oldChatroom['OwnerUin'] = utils.search_dict_list(oldChatroom['MemberList'],
152 | 'UserName', oldChatroom['ChatRoomOwner']).get('Uin', 0)
153 | # - update isAdmin
154 | if 'OwnerUin' in oldChatroom and oldChatroom['OwnerUin'] != 0:
155 | oldChatroom['isAdmin'] = \
156 | oldChatroom['OwnerUin'] == int(core.loginInfo['wxuin'])
157 | else:
158 | oldChatroom['isAdmin'] = None
159 | # - update self
160 | newSelf = utils.search_dict_list(oldChatroom['MemberList'],
161 | 'UserName', core.storageClass.userName)
162 | oldChatroom['self'] = newSelf or copy.deepcopy(core.loginInfo['User'])
163 | return {
164 | 'Type' : 'System',
165 | 'Text' : [chatroom['UserName'] for chatroom in l],
166 | 'SystemInfo' : 'chatrooms',
167 | 'FromUserName' : core.storageClass.userName,
168 | 'ToUserName' : core.storageClass.userName, }
169 |
170 | def update_local_friends(core, l):
171 | '''
172 | get a list of friends or mps for updating local contact
173 | '''
174 | fullList = core.memberList + core.mpList
175 | for friend in l:
176 | if 'NickName' in friend:
177 | utils.emoji_formatter(friend, 'NickName')
178 | if 'DisplayName' in friend:
179 | utils.emoji_formatter(friend, 'DisplayName')
180 | oldInfoDict = utils.search_dict_list(
181 | fullList, 'UserName', friend['UserName'])
182 | if oldInfoDict is None:
183 | oldInfoDict = copy.deepcopy(friend)
184 | if oldInfoDict['VerifyFlag'] & 8 == 0:
185 | core.memberList.append(oldInfoDict)
186 | else:
187 | core.mpList.append(oldInfoDict)
188 | else:
189 | update_info_dict(oldInfoDict, friend)
190 |
191 | def update_local_uin(core, msg):
192 | '''
193 | content contains uins and StatusNotifyUserName contains username
194 | they are in same order, so what I do is to pair them together
195 |
196 | I caught an exception in this method while not knowing why
197 | but don't worry, it won't cause any problem
198 | '''
199 | uins = re.search('([^<]*?)<', msg['Content'])
200 | usernameChangedList = []
201 | r = {
202 | 'Type': 'System',
203 | 'Text': usernameChangedList,
204 | 'SystemInfo': 'uins', }
205 | if uins:
206 | uins = uins.group(1).split(',')
207 | usernames = msg['StatusNotifyUserName'].split(',')
208 | if 0 < len(uins) == len(usernames):
209 | for uin, username in zip(uins, usernames):
210 | if not '@' in username: continue
211 | fullContact = core.memberList + core.chatroomList + core.mpList
212 | userDicts = utils.search_dict_list(fullContact,
213 | 'UserName', username)
214 | if userDicts:
215 | if userDicts.get('Uin', 0) == 0:
216 | userDicts['Uin'] = uin
217 | usernameChangedList.append(username)
218 | logger.debug('Uin fetched: %s, %s' % (username, uin))
219 | else:
220 | if userDicts['Uin'] != uin:
221 | logger.debug('Uin changed: %s, %s' % (
222 | userDicts['Uin'], uin))
223 | else:
224 | if '@@' in username:
225 | update_chatroom(core, username)
226 | newChatroomDict = utils.search_dict_list(
227 | core.chatroomList, 'UserName', username)
228 | if newChatroomDict is None:
229 | newChatroomDict = utils.struct_friend_info({
230 | 'UserName': username,
231 | 'Uin': uin, })
232 | core.chatroomList.append(newChatroomDict)
233 | else:
234 | newChatroomDict['Uin'] = uin
235 | elif '@' in username:
236 | update_friend(core, username)
237 | newFriendDict = utils.search_dict_list(
238 | core.memberList, 'UserName', username)
239 | newFriendDict['Uin'] = uin
240 | usernameChangedList.append(username)
241 | logger.debug('Uin fetched: %s, %s' % (username, uin))
242 | else:
243 | logger.debug('Wrong length of uins & usernames: %s, %s' % (
244 | len(uins), len(usernames)))
245 | else:
246 | logger.debug('No uins in 51 message')
247 | logger.debug(msg['Content'])
248 | return r
249 |
250 | def get_contact(self, update=False):
251 | if not update: return copy.deepcopy(self.chatroomList)
252 | url = '%s/webwxgetcontact?r=%s&seq=0&skey=%s' % (self.loginInfo['url'],
253 | int(time.time()), self.loginInfo['skey'])
254 | headers = {
255 | 'ContentType': 'application/json; charset=UTF-8',
256 | 'User-Agent' : config.USER_AGENT, }
257 | r = self.s.get(url, headers=headers)
258 | tempList = json.loads(r.content.decode('utf-8', 'replace'))['MemberList']
259 | chatroomList, otherList = [], []
260 | for m in tempList:
261 | if m['Sex'] != 0:
262 | otherList.append(m)
263 | elif '@@' in m['UserName']:
264 | chatroomList.append(m)
265 | elif '@' in m['UserName']:
266 | # mp will be dealt in update_local_friends as well
267 | otherList.append(m)
268 | if chatroomList: update_local_chatrooms(self, chatroomList)
269 | if otherList: update_local_friends(self, otherList)
270 | return copy.deepcopy(chatroomList)
271 |
272 | def get_friends(self, update=False):
273 | if update: self.get_contact(update=True)
274 | return copy.deepcopy(self.memberList)
275 |
276 | def get_chatrooms(self, update=False, contactOnly=False):
277 | if contactOnly:
278 | return self.get_contact(update=True)
279 | else:
280 | if update: self.get_contact(True)
281 | return copy.deepcopy(self.chatroomList)
282 |
283 | def get_mps(self, update=False):
284 | if update: self.get_contact(update=True)
285 | return copy.deepcopy(self.mpList)
286 |
287 | def set_alias(self, userName, alias):
288 | oldFriendInfo = utils.search_dict_list(
289 | self.memberList, 'UserName', userName)
290 | if oldFriendInfo is None:
291 | return ReturnValue({'BaseResponse': {
292 | 'Ret': -1001, }})
293 | url = '%s/webwxoplog?lang=%s&pass_ticket=%s' % (
294 | self.loginInfo['url'], 'zh_CN', self.loginInfo['pass_ticket'])
295 | data = {
296 | 'UserName' : userName,
297 | 'CmdId' : 2,
298 | 'RemarkName' : alias,
299 | 'BaseRequest' : self.loginInfo['BaseRequest'], }
300 | headers = { 'User-Agent' : config.USER_AGENT }
301 | r = self.s.post(url, json.dumps(data, ensure_ascii=False).encode('utf8'),
302 | headers=headers)
303 | r = ReturnValue(rawResponse=r)
304 | if r: oldFriendInfo['RemarkName'] = alias
305 | return r
306 |
307 | def set_pinned(self, userName, isPinned=True):
308 | url = '%s/webwxoplog?pass_ticket=%s' % (
309 | self.loginInfo['url'], self.loginInfo['pass_ticket'])
310 | data = {
311 | 'UserName' : userName,
312 | 'CmdId' : 3,
313 | 'OP' : int(isPinned),
314 | 'BaseRequest' : self.loginInfo['BaseRequest'], }
315 | headers = { 'User-Agent' : config.USER_AGENT }
316 | r = self.s.post(url, json=data, headers=headers)
317 | return ReturnValue(rawResponse=r)
318 |
319 | def add_friend(self, userName, status=2, verifyContent='', autoUpdate=True):
320 | ''' Add a friend or accept a friend
321 | * for adding status should be 2
322 | * for accepting status should be 3
323 | '''
324 | url = '%s/webwxverifyuser?r=%s&pass_ticket=%s' % (
325 | self.loginInfo['url'], int(time.time()), self.loginInfo['pass_ticket'])
326 | data = {
327 | 'BaseRequest': self.loginInfo['BaseRequest'],
328 | 'Opcode': status, # 3
329 | 'VerifyUserListSize': 1,
330 | 'VerifyUserList': [{
331 | 'Value': userName,
332 | 'VerifyUserTicket': '', }],
333 | 'VerifyContent': verifyContent,
334 | 'SceneListCount': 1,
335 | 'SceneList': 33, # [33]
336 | 'skey': self.loginInfo['skey'], }
337 | headers = {
338 | 'ContentType': 'application/json; charset=UTF-8',
339 | 'User-Agent' : config.USER_AGENT }
340 | r = self.s.post(url, headers=headers,
341 | data=json.dumps(data, ensure_ascii=False).encode('utf8', 'replace'))
342 | if autoUpdate: self.update_friend(userName)
343 | return ReturnValue(rawResponse=r)
344 |
345 | def get_head_img(self, userName=None, chatroomUserName=None, picDir=None):
346 | ''' get head image
347 | * if you want to get chatroom header: only set chatroomUserName
348 | * if you want to get friend header: only set userName
349 | * if you want to get chatroom member header: set both
350 | '''
351 | params = {
352 | 'userName': userName or chatroomUserName or self.storageClass.userName,
353 | 'skey': self.loginInfo['skey'], }
354 | url = '%s/webwxgeticon' % self.loginInfo['url']
355 | if chatroomUserName is None:
356 | infoDict = self.storageClass.search_friends(userName=userName)
357 | if infoDict is None:
358 | return ReturnValue({'BaseResponse': {
359 | 'ErrMsg': 'No friend found',
360 | 'Ret': -1001, }})
361 | else:
362 | if userName is None:
363 | url = '%s/webwxgetheadimg' % self.loginInfo['url']
364 | else:
365 | chatroom = self.storageClass.search_chatrooms(userName=chatroomUserName)
366 | if chatroomUserName is None:
367 | return ReturnValue({'BaseResponse': {
368 | 'ErrMsg': 'No chatroom found',
369 | 'Ret': -1001, }})
370 | if chatroom['EncryChatRoomId'] == '':
371 | chatroom = self.update_chatroom(chatroomUserName)
372 | params['chatroomid'] = chatroom['EncryChatRoomId']
373 | headers = { 'User-Agent' : config.USER_AGENT }
374 | r = self.s.get(url, params=params, stream=True, headers=headers)
375 | tempStorage = io.BytesIO()
376 | for block in r.iter_content(1024):
377 | tempStorage.write(block)
378 | if picDir is None:
379 | return tempStorage.getvalue()
380 | with open(picDir, 'wb') as f: f.write(tempStorage.getvalue())
381 | return ReturnValue({'BaseResponse': {
382 | 'ErrMsg': 'Successfully downloaded',
383 | 'Ret': 0, }})
384 |
385 | def create_chatroom(self, memberList, topic=''):
386 | url = '%s/webwxcreatechatroom?pass_ticket=%s&r=%s' % (
387 | self.loginInfo['url'], self.loginInfo['pass_ticket'], int(time.time()))
388 | data = {
389 | 'BaseRequest': self.loginInfo['BaseRequest'],
390 | 'MemberCount': len(memberList),
391 | 'MemberList': [{'UserName': member['UserName']} for member in memberList],
392 | 'Topic': topic, }
393 | headers = {
394 | 'content-type': 'application/json; charset=UTF-8',
395 | 'User-Agent' : config.USER_AGENT }
396 | r = self.s.post(url, headers=headers,
397 | data=json.dumps(data, ensure_ascii=False).encode('utf8', 'ignore'))
398 | return ReturnValue(rawResponse=r)
399 |
400 | def set_chatroom_name(self, chatroomUserName, name):
401 | url = '%s/webwxupdatechatroom?fun=modtopic&pass_ticket=%s' % (
402 | self.loginInfo['url'], self.loginInfo['pass_ticket'])
403 | data = {
404 | 'BaseRequest': self.loginInfo['BaseRequest'],
405 | 'ChatRoomName': chatroomUserName,
406 | 'NewTopic': name, }
407 | headers = {
408 | 'content-type': 'application/json; charset=UTF-8',
409 | 'User-Agent' : config.USER_AGENT }
410 | r = self.s.post(url, headers=headers,
411 | data=json.dumps(data, ensure_ascii=False).encode('utf8', 'ignore'))
412 | return ReturnValue(rawResponse=r)
413 |
414 | def delete_member_from_chatroom(self, chatroomUserName, memberList):
415 | url = '%s/webwxupdatechatroom?fun=delmember&pass_ticket=%s' % (
416 | self.loginInfo['url'], self.loginInfo['pass_ticket'])
417 | data = {
418 | 'BaseRequest': self.loginInfo['BaseRequest'],
419 | 'ChatRoomName': chatroomUserName,
420 | 'DelMemberList': ','.join([member['UserName'] for member in memberList]), }
421 | headers = {
422 | 'content-type': 'application/json; charset=UTF-8',
423 | 'User-Agent' : config.USER_AGENT}
424 | r = self.s.post(url, data=json.dumps(data),headers=headers)
425 | return ReturnValue(rawResponse=r)
426 |
427 | def add_member_into_chatroom(self, chatroomUserName, memberList,
428 | useInvitation=False):
429 | ''' add or invite member into chatroom
430 | * there are two ways to get members into chatroom: invite or directly add
431 | * but for chatrooms with more than 40 users, you can only use invite
432 | * but don't worry we will auto-force userInvitation for you when necessary
433 | '''
434 | if not useInvitation:
435 | chatroom = self.storageClass.search_chatrooms(userName=chatroomUserName)
436 | if not chatroom: chatroom = self.update_chatroom(chatroomUserName)
437 | if len(chatroom['MemberList']) > self.loginInfo['InviteStartCount']:
438 | useInvitation = True
439 | if useInvitation:
440 | fun, memberKeyName = 'invitemember', 'InviteMemberList'
441 | else:
442 | fun, memberKeyName = 'addmember', 'AddMemberList'
443 | url = '%s/webwxupdatechatroom?fun=%s&pass_ticket=%s' % (
444 | self.loginInfo['url'], fun, self.loginInfo['pass_ticket'])
445 | params = {
446 | 'BaseRequest' : self.loginInfo['BaseRequest'],
447 | 'ChatRoomName' : chatroomUserName,
448 | memberKeyName : ','.join([member['UserName'] for member in memberList]), }
449 | headers = {
450 | 'content-type': 'application/json; charset=UTF-8',
451 | 'User-Agent' : config.USER_AGENT}
452 | r = self.s.post(url, data=json.dumps(params),headers=headers)
453 | return ReturnValue(rawResponse=r)
454 |
--------------------------------------------------------------------------------