diff --git a/README.md b/README.md
index 09d6a2e..1be21b3 100644
--- a/README.md
+++ b/README.md
@@ -1,98 +1,67 @@
# lykchat信息发送系统
lykchat信息发送系统是Python3开发的,通过模拟微信网页端,基于个人微信号,为系统管理人员提供信息发送工具。
+
实现的功能有用户登录管理、微信登陆管理和微信信息发送功能。
-## 特点 ##
+# 通知
+在2017年7月20日晚上,疑似微信web端做了调整,web上不在显示好友微信号,所以即日使用微信号发送信息可能提示无法找到好友等错误提示。
+
+解决办法:
+
+ 1、使用好友昵称来发送信息
+ 2、使用备注名来发送信息
+ 但必须只能是数字、字母、符号等,不能为图片等
+
+## 特点
1、简单高效
基于个人微信号,模拟微信web端,部署和维护简单
web管理页面实现可视化管理微信登陆
接口采用URL,简化调用复杂度,返回结果均为json格式
- 2、信息共享
- 通过共享用户session和微信登陆信息,保证系统长期稳定运行
+ 2、信息共享
+ 通过共享用户session和微信登陆信息,保证系统长期稳定运行
3、7*24不间断服务
- 计划任务定时检查微信登陆状态,微信保持登陆超过20天
- 4、用户管理
+ 计划任务定时检查微信登陆状态,微信保持登陆超过20天(有用户反映,保持登陆超过30天后,会被微信封掉,解决办法是登陆后2~3个星期退出登陆一次)
+ 4、支持发送多媒体信息
+ 除了支持发送纯文字信息外,还支持发送图片、视频、文件等信息
+ 5、用户管理
通过用户隔离微信个人号,不同用户管理不同微信号
用户密码分为管理密码和接口密码,保证用户信息安全性
- 5、微信信息安全
+ 6、微信信息安全
不会监控和存储微信聊天信息
不会增加和删除好友
-## 截图 ##
-
-管理页面--等待扫码
-
-
+## 截图
管理页面--功能展示
+

-管理页面--微信登陆时长
-
+管理页面--微信登陆时长
+
+
接口-发送信息成功
-
-
+
+
+
+## 发送信息接口使用说明
+[https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E](https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E "发送信息接口")
-## 模块 ##
+## 模块和工作流程
+[https://github.com/lykops/lykchat/wiki/%E6%A8%A1%E5%9D%97%E5%92%8C%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B](https://github.com/lykops/lykchat/wiki/%E6%A8%A1%E5%9D%97%E5%92%8C%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B "模块和工作流程")
- 1、管理web页面:可视化管理微信个人号
- 用户登录和认证
- 微信号登陆管理
- 发送信息给好友
- 2、发送信息接口:
- 通过接口方式为其他业务系统发送信息给指定好友
- 3、计划任务检测微信登陆状态:
- 获取所有登录微信成功的用户,通过调用检测微信登陆接口
- 4、会话保持模块:
- 存储微信登陆信息和会话信息,同用户在任何地方登陆,保证微信登陆状态一致
- 5、模拟微信web端模块:
- 通过微信登陆信息,访问微信web端接口,实现管理登陆、发送信息等功能。
-
-
-## V2.0.0版本说明 ##
-
- 1、修复bug:
- 微信登陆时间超过12小时自动退出,测试过程中测得最大登陆时长20天
- 2、完善功能:
- 1)、微信会话保持机制:
- 保存位置:之前保存在数据库中,修改为数据库只记录用户名,所有信息保持到文件中,减少数据库的查询、写入、加解密压力
- 动态更新微信登陆信息
- 调整会话信息内容
- 2)、优化微信检测登陆流程,大大缩短各个页面执行时间
- 3)、完善获取好友流程
- 3、新增功能:
- 1)、增加用户管理机制
- 2)、好友信息缓存机制
- 4、取消功能:
- 接受和处理新信息
-
-
-## 发送信息接口 ##
- URL地址:http://IP(或者域名)/sendmsg
- 支持post和get方法
- 请求参数说明:
- 'username' : 管理用户,同管理web页面,通过用户确认微信发送者
- 'pwd' : 接口密码,注意不等于登陆密码,
- 'friendfield':接受信息的好友字段代号,0昵称,1微信号,2备注名,可以为空,默认为0
- 'friend': 接受信息的好友的昵称、微信号、备注名的其中之一,不能为空
- 'content': 发送内容,不能为空
- 注意:
- friend一定是该用户下的登陆微信好友列表中的
- friendfield最好是微信号(Alias),也可以使用昵称(NickName)或者备注名(RemarkName)(但不能重复出现)
- 由于好友列表使用缓存机制,新增好友可能发送信息不成功
- 返回信息:
- json格式,{'Msg': 执行结果, 'Code':返回代码, 'ErrMsg':如果-1005返回参数列表,其他发送微信返回信息}
- 常见code:0成功;-1101参数错误;-1102无法找到好友;1101微信号退出登录,其他为微信返回错误
- 例子:http://192.168.100.104/sendmsg?username=zabbix&pwd=123456&friendfield=1&friend=lyk-ops&content=test
+## 安装手册
+[https://github.com/lykops/lykchat/wiki/%E5%AE%89%E8%A3%85%E6%89%8B%E5%86%8C](https://github.com/lykops/lykchat/wiki/%E5%AE%89%E8%A3%85%E6%89%8B%E5%86%8C "安装手册")
+## ChangeLog
+[https://github.com/lykops/lykchat/wiki/ChangeLog](https://github.com/lykops/lykchat/wiki/ChangeLog "ChangeLog")
-## 说明 ##
+## 说明
1、作者尽可能通过严谨测试来验证系统功能,但由于专业水平有限,无法避免出现bug。
2、该项目是基于微信web端进行开发的
@@ -109,6 +78,3 @@ lykchat信息发送系统是Python3开发的,通过模拟微信网页端,基
邮箱:liyingke112@126.com
-
-
-
diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md
new file mode 100644
index 0000000..a267461
--- /dev/null
+++ b/doc/ChangeLog.md
@@ -0,0 +1,32 @@
+# V2.1.0
+## 升级内容
+ 新增发送图片、视频、文件等多媒体信息
+## 从v2.0.0更新步骤
+ 1、下载最新版本
+ 2、安装依赖包
+ /usr/local/python36/bin/pip3 install -r /opt/lykchat/install/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
+ 3、修改配置文件
+ 配置文件library/config/wechat.py
+ 新增上传文件最大数max_upload_size(默认为5M,建议不要上传文件太大,导致访问接口超时)
+ 4、修改nginx的上传文件最大值
+ client_max_body_size 10m;
+## 说明事项
+ django默认启用防CSRF(Cross-site request forgery跨站请求伪造),导致无法使用post方法调用该接口,所以作者强制关闭了防csrf功能。
+ 如果你觉得有安全隐患,又不需要发送多媒体文件,请下载2.0版本:https://codeload.github.com/lykops/lykchat/zip/master
+
+# V2.0.0
+
+ 1、修复bug:
+ 微信登陆时间超过12小时自动退出,测试过程中测得最大登陆时长20天
+ 2、完善功能:
+ 1)、微信会话保持机制:
+ 保存位置:之前保存在数据库中,修改为数据库只记录用户名,所有信息保持到文件中,减少数据库的查询、写入、加解密压力
+ 动态更新微信登陆信息
+ 调整会话信息内容
+ 2)、优化微信检测登陆流程,大大缩短各个页面执行时间
+ 3)、完善获取好友流程
+ 3、新增功能:
+ 1)、增加用户管理机制
+ 2)、好友信息缓存机制
+ 4、取消功能:
+ 接受和处理新信息
\ No newline at end of file
diff --git "a/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md" "b/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md"
index 6bc734c..daa0090 100644
--- "a/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md"
+++ "b/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md"
@@ -1,7 +1,3 @@
-# lykchat工作流程
-
-
-
# 模块说明
## 管理web页面
@@ -12,26 +8,15 @@
发送信息给好友:用于测试发送功能是否可用
通过选择好友列表显示获取需要发送信息的好友
好友信息列表只展示文件传输助手、除了自己外的好友(疑似好友表示没有设置该好友没有设置性别)、部分群(是根据第一页好友信息获取的),自动屏蔽掉公众号、微信系统用户、好友为自己。
-## 发送信息接口
- 通过接口方式为其他业务系统发送信息给指定好友
- URL地址:http://IP(或者域名)/sendmsg
- 支持post和get方法
- 请求参数说明:
- 'username' : 管理用户,同管理web页面,通过用户确认微信发送者
- 'pwd' : 接口密码,注意不等于登陆密码
- 'friendfield':接受信息的好友字段代号,{0:"NickName" , 1:"Alias" , 2:"RemarkName"},可以为空,默认为0
- 'friend': 接受信息的好友的昵称、微信号、备注名的其中之一,不能为空
- 'content': 发送内容,不能为空
- 注意:
- friend一定是该用户下的登陆微信好友列表中的
- friendfield最好是微信号(Alias),也可以使用昵称(NickName)或者备注名(RemarkName),但不能重复
- 返回信息:
- json格式,{'Msg': 执行结果, 'Code':返回代码, 'ErrMsg':如果-1005返回参数列表,其他发送微信返回信息}
- 常见code:0成功,-1101参数错误,-1102无法找到好友,1101微信号退出登录,其他为微信返回错误
- 例子:http://192.168.100.104/sendmsg?username=zabbix&pwd=123456&friendfield=1&friend=lyk-ops&content=test
+ 上传需要发送的文件
+
+## 发送信息接口 ##
+[https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E](https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E "发送信息接口")
+
## 计划任务
检测微信登陆状态:
获取所有登录微信成功的用户,通过调用检测微信登陆接口
+
## 会话保持模块
存储微信登陆信息和会话信息,同用户在任何地方登陆,保证微信登陆状态一致
访问管理页面和微信登陆检测接口,根据session或者参数获取用户名,然后读取会话文件,页面操作后,再一次更新数据库和会话文件
@@ -47,7 +32,10 @@
json格式
每次访问更新
默认存放在/dev/shm/lykchat下,根据用户名命名
+
## 模拟微信web端模块
它是该系统的核心和底层模块。
通过微信登陆信息,访问微信web端接口,实现管理登陆、发送信息等功能。
+# lykchat工作流程
+
\ No newline at end of file
diff --git "a/doc/\345\217\221\351\200\201\344\277\241\346\201\257\346\216\245\345\217\243\350\257\264\346\230\216.md" "b/doc/\345\217\221\351\200\201\344\277\241\346\201\257\346\216\245\345\217\243\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..b4935c4
--- /dev/null
+++ "b/doc/\345\217\221\351\200\201\344\277\241\346\201\257\346\216\245\345\217\243\350\257\264\346\230\216.md"
@@ -0,0 +1,52 @@
+发送信息接口为其他业务系统发送信息给指定好友
+
+# 特点
+ 支持post和get方法
+ 除了支持纯文字外,还支持图片、文字等诸多多媒体文件。
+
+# 注意
+ django默认启用防CSRF(Cross-site request forgery跨站请求伪造),导致无法使用post方法调用该接口,所以作者强制关闭了防csrf功能。
+ 如果你觉得有安全隐患,又不需要发送多媒体文件,请下载2.0版本:https://codeload.github.com/lykops/lykchat/zip/master
+
+# 参数说明
+ 下面参数支持post和get方法
+ 'username' :
+ 管理用户,同管理web页面,通过用户确认微信发送者
+ 'pwd' :
+ 接口密码,注意不等于登陆密码
+ 'type' :
+ '发送信息类型
+ 可选{"txt":"纯文字" ,"img":"图片","file":"发送文件","video":"视频"},可以为空
+ 默认:不发送文件为txt,发送文件为file
+ 'fromalias':
+ 保留字段
+ 发送者的微信号,目前没有使用该参数
+ 'friendfield':
+ 接受者的字段代号,可选{0:"NickName" , 1:"Alias" , 2:"RemarkName"},可以为空
+ 默认为0'
+ 'friend':
+ 接受者的昵称、微信号、备注名的其中一个,不能为空
+ 'content':
+ 发送内容,不能为空
+ 必须是post方法:
+ 'file':
+ 需要发送的文件,
+ 注意:
+ friend一定是该用户下的登陆微信好友列表中的
+ friendfield最好是微信号(Alias),也可以使用昵称(NickName)或者备注名(RemarkName),但不能重复
+
+# 返回信息
+ json格式,{'Msg': 执行结果, 'Code':返回代码, 'ErrMsg':如果-1005返回参数列表,其他发送微信返回信息}
+ 常见code:0成功,-1101参数错误,-1102无法找到好友,1101微信号退出登录,其他为微信返回错误
+
+# 使用实例
+ 接口URL地址:http://IP(或者域名)/sendmsg
+ 发送纯文字:
+ http://192.168.100.104/sendmsg?username=zabbix&pwd=123456&friendfield=1&friend=lyk-ops&content=test
+ 发送多媒体:
+ 方法1:Linux的curl命令
+ curl -F "file=@/root/a" 'http://127.0.0.1/sendmsg?username=zabbix&pwd=123456&type=img&friendfield=1&friend=lyk-ops&content=test'
+ 方法2:python脚本test_sendfile.py
+ 请参照该项目的根目录python脚本test_sendfile.py,执行
+ /usr/local/python36/bin/python3 /opt/lykchat/test_upload.py "{'username':'zabbix','pwd':'123456','type':'img','friendfield':'1','friend':'lyk-ops','content':'恭喜发财','file':'/root/b.jpg'}"
+
\ No newline at end of file
diff --git "a/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md" "b/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md"
index ed00a43..073be93 100644
--- "a/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md"
+++ "b/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md"
@@ -6,7 +6,7 @@
Python3+django1.10
测试环境使用Python3.5.2、3.6.0两个版本测试
## web服务器
- Nginx
+ Nginx
主要解决静态文件展示。
测试环境为nginx 1.10.2
## 数据库
@@ -21,7 +21,7 @@
## 安装依赖包
yum install -y epel-release
- yum install telnet ntpdate lrzsz bash glibc openssl vim automake autoconf gcc xz ncurses-devel patch python-devel git python-pip gcc-c++ redhat-rpm-config -y
+ yum install telnet ntpdate lrzsz bash glibc openssl vim automake autoconf gcc xz ncurses-devel patch python-devel git python-pip gcc-c++ redhat-rpm-config openssl-devel openssl-static openssl098e openssl-libs -y
yum upgrade -y
## 配置nginx
@@ -43,7 +43,7 @@
在本地安装mysql
rpm -ivh http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm
- yum install mysql-community-* mysql-connector-python* --skip-broke
+ yum install mysql-community-client mysql-community-common mysql-community-devel mysql-community-libs mysql-community-libs-compat mysql-community-server --skip-broke
新增一个数据库lykchat
设置用户lykchat,密码为!QAZ2wsx,把数据库lykchat的权限分配给用户lykchat
@@ -75,7 +75,7 @@
## 初始化数据库和配置计划任务
/usr/local/python36/bin/python3 /opt/lykchat/manage.py makemigrations
- /usr/local/python36/bin/python3 /opt/lykchat/manage.py migrat
+ /usr/local/python36/bin/python3 /opt/lykchat/manage.py migrate
/usr/local/python36/bin/python3 /opt/lykchat/manage.py crontab add
crontab -l
diff --git "a/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg" "b/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg"
index a27bb97..9b12ede 100644
Binary files "a/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg" and "b/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg" differ
diff --git a/install/requirements.txt b/install/requirements.txt
index 33732ff..e1d2b04 100644
--- a/install/requirements.txt
+++ b/install/requirements.txt
@@ -8,4 +8,5 @@ djangorestframework
django-template-utils
mysqlclient
Pillow
-requests
\ No newline at end of file
+requests
+requests-toolbelt
\ No newline at end of file
diff --git a/library/config/wechat.py b/library/config/wechat.py
index 474e429..a979929 100644
--- a/library/config/wechat.py
+++ b/library/config/wechat.py
@@ -1,7 +1,7 @@
import os, platform
from lykchat.settings import BASE_DIR
-version = '2.0.0'
+version = '2.1.0'
base_url = 'https://wx2.qq.com'
os_type = platform.system() # Windows, Linux, Darwin
curr_dir = os.getcwd()
@@ -68,14 +68,15 @@
},
}
-
SESSION_COOKIE_AGE = 60 * 60 * 1
+max_upload_size = 1024 * 1024 * 5
+# 上传文件最大值,单位bytes,默认5M
CRONJOBS = (
('*/2 * * * *', 'library.cron.checklogin.check_login', '>>/dev/shm/lykchat.txt 2>&1'),
)
-
+# 检测登陆状态的计划任务
url_frond = 'http://127.0.0.1/'
diff --git a/library/file/__init__.py b/library/file/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/library/file/get_md5.py b/library/file/get_md5.py
new file mode 100644
index 0000000..ed164a3
--- /dev/null
+++ b/library/file/get_md5.py
@@ -0,0 +1,16 @@
+import hashlib, os
+
+def get_file_md5(filename):
+ if not os.path.isfile(filename):
+ return False
+
+ myhash = hashlib.md5()
+ f = open(filename, 'rb')
+ while True:
+ b = f.read(8096)
+ if not b :
+ break
+ myhash.update(b)
+ f.close()
+
+ return myhash.hexdigest()
diff --git a/library/file/upload.py b/library/file/upload.py
new file mode 100644
index 0000000..8358353
--- /dev/null
+++ b/library/file/upload.py
@@ -0,0 +1,49 @@
+import os, time
+from lykchat.settings import BASE_DIR
+
+def upload_file(file, filename='', username=''):
+ timestr = time.strftime('%Y%m%d' , time.localtime())
+ timestr = str(timestr)
+ logfile = os.path.join(BASE_DIR, 'file/upload/index.txt')
+
+ upload_dir = os.path.join(BASE_DIR, 'file/upload/' + timestr + '/')
+ if not os.path.exists(upload_dir):
+ try :
+ os.mkdir(upload_dir)
+ except :
+ os.makedirs(upload_dir)
+
+ '''
+ if filename == '' or not filename :
+ secstr = time.strftime('%H%M%S' , time.localtime())
+ secstr = str(secstr)
+ randomstr = random.randint(1000, 9999)
+ randomstr = str(randomstr)
+ filename = timestr + '-' + secstr + randomstr
+ else :
+ filename = upload_dir + str(filename)
+ '''
+
+ datetimestr = time.strftime('%Y-%m-%d %H:%M:%S' , time.localtime())
+ datetimestr = str(datetimestr)
+ log = str(username) + ' ' + datetimestr + ' ' + filename + '\n'
+
+ filename = upload_dir + str(filename)
+ secstr = time.strftime('%H%M%S' , time.localtime())
+ secstr = str(secstr)
+ if os.path.exists(filename):
+ os.rename(filename , filename + '-' + timestr + '-' + secstr)
+
+
+ with open(filename, 'wb+') as dest:
+ for chunk in file.chunks():
+ dest.write(chunk)
+
+ # os.system('chmod 444 ' + filename)
+ open(logfile, 'a').write(log)
+ return filename
+
+ return False
+
+
+
diff --git a/library/visit_url/request/cookie.py b/library/visit_url/request/cookie.py
index 8ad1723..950d069 100644
--- a/library/visit_url/request/cookie.py
+++ b/library/visit_url/request/cookie.py
@@ -4,20 +4,20 @@ class Request_Url():
'''
使用requests模块访问web页面,必须提供url
'''
- def __init__(self, url , headers={} , cookies={} , data={} , params={} , allow_redirects=True):
+ def __init__(self, url, headers={}, cookies={}, data={}, files={}, params={}, allow_redirects=True):
default_headers = {
'Accept' : 'application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding' : 'gzip, deflate, br',
'charset': 'UTF-8',
'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.5,zh;q=0.3',
'Connection': 'keep-alive',
- 'Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0',
+ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0',
}
if headers == {} :
headers = default_headers
else :
- for header in ['Accept' ,'Accept-Encoding' ,'Accept-Language','Connection', 'Agent']:
+ for header in ['Accept' , 'Accept-Encoding' , 'Accept-Language', 'Connection', 'User-Agent']:
if header not in headers or headers[header] == '':
headers[header] = default_headers[header]
# 新增headers
@@ -33,6 +33,8 @@ def __init__(self, url , headers={} , cookies={} , data={} , params={} , allow_r
if data != {} :
url_req = requests.post(self.url, headers=headers , data=data, cookies=cookies , allow_redirects=allow_redirects)
# post方法
+ elif files != {} :
+ url_req = requests.post(self.url, data=files, headers=headers, timeout=300)
else :
url_req = requests.get(self.url, headers=headers, cookies=cookies , allow_redirects=allow_redirects, params=params)
# get方法
@@ -63,6 +65,7 @@ def __init__(self, url , headers={} , cookies={} , data={} , params={} , allow_r
self.cookies = cookies
# 指定cookie,用于后续访问,微信中需要保持session即可
+
def return_web_request_base_dict(self):
return {'headers':self.headers , 'cookies':self.cookies}
diff --git a/library/wechat/send.py b/library/wechat/send.py
index d0c33a1..660add2 100644
--- a/library/wechat/send.py
+++ b/library/wechat/send.py
@@ -1,10 +1,13 @@
-import json, re
-import time
+import json, re, time, mimetypes, os
+import random
+
+from requests_toolbelt.multipart.encoder import MultipartEncoder
from library.config import wechat
+from library.file.get_md5 import get_file_md5
from library.visit_url.request.cookie import Request_Url
-from .friend import Get_Friend
+from .friend import Get_Friend
class Send_Msg():
@@ -15,13 +18,13 @@ def __init__(self, session_info_dict):
self.session_info_dict = session_info_dict
self.login_info = self.session_info_dict['login_info']
self.web_request_base_dict = self.session_info_dict['web_request_base_dict']
- self.base_url = self.login_info['url']
self.base_request = self.login_info['BaseRequest']
self.myself = self.session_info_dict['myself']
self.msgid = int(time.time() * 1000 * 1000 * 10)
+ self.pass_ticket = self.login_info['pass_ticket']
- def send(self, content, msgType='Test Message', tousername='filehelper' , post_field='UserName'):
+ def send(self, content, msgType='txt', filename='', tousername='filehelper' , post_field='UserName'):
'''
发送信息,返回类型为字典
'''
@@ -35,12 +38,216 @@ def send(self, content, msgType='Test Message', tousername='filehelper' , post_f
if not re.search('@', tousername) and tousername != 'filehelper':
return {'Msg': '发送失败,账号设置错误', 'Code':-1102, 'ErrMsg':'无法找到好友'}
+
+ if msgType != 'txt' and (filename != '' and filename) :
+ result_dict = self._upload_media(filename, tousername, msgType, content)
+ return result_dict
+
+ result_dict = self._send_text(tousername, content)
+ return result_dict
+
+
+ def _upload_media(self, filename, tousername, msgType, content):
+ base_url = self.session_info_dict['login_info']['file_url']
+ url = base_url + '/webwxuploadmedia?f=json'
+
+ name = os.path.basename(filename) # 文件名
+ mime_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream' # MIME格式,注意是根据文件后缀名来确认的
+ media_type = re.split(r'/' , mime_type)[0] or 'application'
+
+ # 微信识别的文档格式
+ if media_type == 'image' :
+ media_type = 'pic'
+ elif media_type == 'audio' or media_type == 'video' :
+ media_type = 'video'
+ else :
+ media_type = 'doc'
+
+ # 当用户上传类型设置为file时,强制media_type设置为file
+ # if msgType == 'file' :
+ # media_type = 'doc'
+
+ modts = os.path.getmtime(filename) # 文件修改日期
+ modstr = time.localtime(modts)
+ lastModifieDate = time.strftime('%a %b %d %Y %H:%M:%S GMT+0800 (CST)', modstr)
+ file_content = open(filename, 'rb')
+ file_size = os.path.getsize(filename) # 文件大小
+ chunksize = 524288 # 每个分开大小
+ chunks = int((file_size - 1) / chunksize) + 1
+ webwx_data_ticket = self.web_request_base_dict['cookies']['webwx_data_ticket']
+
+ uploadmediarequest = json.dumps({
+ 'UploadType':2,
+ "BaseRequest": self.base_request,
+ "ClientMediaId": int(time.time() * 1000),
+ "TotalLen": file_size,
+ "StartPos": 0,
+ "DataLen": file_size,
+ "MediaType": 4,
+ 'FromUserName':self.myself['UserName'],
+ 'ToUserName':tousername,
+ 'FileMd5':get_file_md5(filename),
+ }, ensure_ascii=False).encode('utf8')
+
+ for chunk in range(chunks):
+ ff = file_content.read(chunksize)
+ if chunks == 1:
+ multipart_encoder = MultipartEncoder(
+ fields={
+ 'id': 'WU_FILE_0',
+ 'name': name,
+ 'type': mime_type,
+ 'lastModifiedDate': lastModifieDate,
+ 'size':str(file_size),
+ 'mediatype': media_type,
+ 'uploadmediarequest': uploadmediarequest,
+ 'webwx_data_ticket': webwx_data_ticket,
+ 'pass_ticket':self.pass_ticket,
+ 'filename': (name , ff, mime_type)
+ },
+ boundary='---------------------------' + str(random.randint(1e28, 1e29 - 1))
+ )
+ else:
+ multipart_encoder = MultipartEncoder(
+ fields={
+ 'id': 'WU_FILE_0',
+ 'name': name,
+ 'type': mime_type,
+ 'lastModifiedDate': lastModifieDate,
+ 'size':str(file_size),
+ 'chunks': str(chunks),
+ 'chunk': str(chunk),
+ 'mediatype': media_type,
+ 'uploadmediarequest': uploadmediarequest,
+ 'webwx_data_ticket': webwx_data_ticket,
+ 'pass_ticket':self.pass_ticket,
+ 'filename': (name , ff, mime_type)
+ },
+ boundary='---------------------------' + str(random.randint(1e28, 1e29 - 1))
+ )
+
+ self.web_request_base_dict['headers']['Origin-Type'] = 'https://wx2.qq.com'
+ self.web_request_base_dict['headers']['Content-Type'] = multipart_encoder.content_type
+
+ open_url = Request_Url(url, files=multipart_encoder, **self.web_request_base_dict)
+ self.web_request_base_dict = open_url.return_web_request_base_dict()
+ url_req = open_url.return_context()
+ web_reselt_dict = json.loads(url_req.text)
+
+ if web_reselt_dict['BaseResponse']['Ret'] != 0 :
+ result_dict = {'Msg': '发送失败,上传文件失败', 'Code':-1008, 'ErrMsg': web_reselt_dict['BaseResponse']}
+ else :
+ mediaid = web_reselt_dict['MediaId']
+ if mediaid :
+ try :
+ if media_type == 'pic' :
+ result_dict = self._send_img(mediaid, tousername, content)
+ elif media_type == 'video':
+ result_dict = self._send_video(mediaid, tousername, content)
+ else :
+ result_dict = self._send_file(mediaid, name, file_size, tousername, content)
+ except :
+ result_dict = {'Msg': '发送失败,发送时出错', 'Code':-1009, 'ErrMsg': {}}
+
+ if result_dict['Code'] != 0 :
+ content = content + '\n文件发送失败,原因:\n' + str(result_dict)
+
+ result_dict = self._send_text(tousername, content)
+ return result_dict
- url = '%s/webwxsendmsg' % self.base_url
- payloads = {
+
+ def _send_img(self, mediaid, tousername, content):
+ '''
+ 发送图片
+ '''
+ base_url = self.login_info['url']
+ url = base_url + '/webwxsendmsgimg?fun=async&f=json&lang=zh_CN'
+ data = {
+ 'BaseRequest':self.base_request,
+ 'Msg':{
+ 'Type':3,
+ 'MediaId' :mediaid,
+ 'Content':content,
+ "FromUserName":self.myself['UserName'],
+ "ToUserName":tousername,
+ "LocalID":self.msgid,
+ "ClientMsgId":self.msgid
+ },
+ "Scene":0
+ }
+ data = json.dumps(data, ensure_ascii=False).encode('utf8')
+ open_url = Request_Url(url, data=data , **self.web_request_base_dict)
+ self.web_request_base_dict = open_url.return_web_request_base_dict()
+ url_req = open_url.return_context()
+ result_dict = self._handle_result(url_req)
+ return result_dict
+
+
+ def _send_file(self, mediaid, name, file_size, tousername, content):
+ '''
+ 发送普通文件
+ '''
+ base_url = self.login_info['url']
+ url = base_url + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket
+ fileend = re.split(r'.', name)[-1] # 后缀名
+ data = {
'BaseRequest': self.base_request,
'Msg': {
- 'Type': msgType,
+ 'Type': 6,
+ 'Content': "