mirror of
https://github.com/smallevilbeast/ntchat.git
synced 2025-06-06 09:36:07 +08:00
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
da155c82e3 | ||
![]() |
92364c1e65 | ||
![]() |
90c824d0b0 | ||
![]() |
330f0724f3 | ||
![]() |
38506a0e7b | ||
![]() |
f7fb727257 | ||
![]() |
ae450372b1 | ||
![]() |
734eab020e | ||
![]() |
89ea755606 | ||
![]() |
cdaacf186b | ||
![]() |
cd51f463bc | ||
![]() |
99ac6bf007 | ||
![]() |
476e8428cb | ||
![]() |
f7ba8fdbbf | ||
![]() |
3f22840334 | ||
![]() |
15b408d390 | ||
![]() |
5ff92ea0c7 | ||
![]() |
cf69a36a72 | ||
![]() |
d91c807905 | ||
![]() |
7a7e927442 | ||
![]() |
9a0cc00493 | ||
![]() |
8db8998aaa | ||
![]() |
38d34ffb97 | ||
![]() |
eac6a6484a | ||
![]() |
6e30f1f243 | ||
![]() |
e279b6e6fc | ||
![]() |
b09ce23997 | ||
![]() |
da213b01af | ||
![]() |
80bc605bfa | ||
![]() |
008e6867ef | ||
![]() |
91d1257220 | ||
![]() |
254123f640 | ||
![]() |
138026a1e4 | ||
![]() |
841b8bf3b5 | ||
![]() |
16369dfea9 | ||
![]() |
7b2db4409e | ||
![]() |
88a6c75f14 | ||
![]() |
f326669603 | ||
![]() |
0c19723833 | ||
![]() |
f0f2e445f2 | ||
![]() |
dd054f1c8b | ||
![]() |
de9fee4682 | ||
![]() |
3ac176ab2d | ||
![]() |
1f8aa49972 | ||
![]() |
8fc68a4bac | ||
![]() |
c9179ed629 | ||
![]() |
f8906a8934 | ||
![]() |
1ed2f6bd7f | ||
![]() |
a4be63cd03 | ||
![]() |
61aa8ed0fc | ||
![]() |
a1864a0456 | ||
![]() |
8fc2592926 | ||
![]() |
4391ad51b1 | ||
![]() |
a95b6ad2dc | ||
![]() |
1e7d0350d5 | ||
![]() |
708cc9e2d9 | ||
![]() |
9505e69779 | ||
![]() |
594cc561cb |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -24,6 +24,8 @@ main.spec
|
||||||
build/
|
build/
|
||||||
config.ini
|
config.ini
|
||||||
ntchat/wc/*.pyd
|
ntchat/wc/*.pyd
|
||||||
|
ntchat/wc/*.dat
|
||||||
wheelhouse/
|
wheelhouse/
|
||||||
setup_conf.py
|
setup_conf.py
|
||||||
upload.bat
|
upload.bat
|
||||||
|
download/
|
27
README.md
27
README.md
|
@ -1,6 +1,6 @@
|
||||||
<h1 align="center">NtChat</h1>
|
<h1 align="center">NtChat</h1>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/smallevilbeast/ntchat/releases"><img src="https://img.shields.io/badge/release-0.1.3-blue.svg?" alt="release"></a>
|
<a href="https://github.com/smallevilbeast/ntchat/releases"><img src="https://img.shields.io/badge/release-0.1.17-blue.svg?" alt="release"></a>
|
||||||
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-brightgreen.svg?" alt="License"></a>
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-brightgreen.svg?" alt="License"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -14,7 +14,13 @@
|
||||||
- 支持好友和群管理
|
- 支持好友和群管理
|
||||||
|
|
||||||
## 支持的微信版本下载
|
## 支持的微信版本下载
|
||||||
- [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe)
|
- 下载 [WeChatSetup3.6.0.18.exe](https://github.com/tom-snow/wechat-windows-versions/releases/download/v3.6.0.18/WeChatSetup-3.6.0.18.exe)
|
||||||
|
|
||||||
|
## 帮助文档
|
||||||
|
- 查看 [常见问题](docs/FAQ.md)
|
||||||
|
- 查看 [常用示例](examples)
|
||||||
|
- 查看 [NtChatHttp接口示例](fastapi_example)
|
||||||
|
- 查看 [PyXCGUI项目](https://github.com/smallevilbeast/pyxcgui)
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
|
@ -120,6 +126,14 @@ except KeyboardInterrupt:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 使用fastapi框架实现的web api接口
|
||||||
|
|
||||||
|
通过fastapi的swagger在线文档可以很方便的管理NtChat接口
|
||||||
|
|
||||||
|
[查看fastapi_example例子](./fastapi_example)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## 使用pyxcgui界面库实现的简单例子
|
## 使用pyxcgui界面库实现的简单例子
|
||||||
|
|
||||||
|
@ -131,7 +145,7 @@ except KeyboardInterrupt:
|
||||||
# -*- coding: utf8 -*-
|
# -*- coding: utf8 -*-
|
||||||
import xcgui
|
import xcgui
|
||||||
import ntchat
|
import ntchat
|
||||||
from xcgui import XApp, XWindow
|
from xcgui import XApp, XWindow, RunUiThread
|
||||||
|
|
||||||
|
|
||||||
class NtChatWindow(XWindow):
|
class NtChatWindow(XWindow):
|
||||||
|
@ -156,6 +170,8 @@ class NtChatWindow(XWindow):
|
||||||
def on_btn_open_clicked(self, sender, _):
|
def on_btn_open_clicked(self, sender, _):
|
||||||
self.wechat_instance = ntchat.WeChat()
|
self.wechat_instance = ntchat.WeChat()
|
||||||
self.wechat_instance.open(smart=True)
|
self.wechat_instance.open(smart=True)
|
||||||
|
|
||||||
|
# 监听所有通知消息
|
||||||
self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message)
|
self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message)
|
||||||
|
|
||||||
def on_btn_send_clicked(self, sender, _):
|
def on_btn_send_clicked(self, sender, _):
|
||||||
|
@ -167,6 +183,7 @@ class NtChatWindow(XWindow):
|
||||||
else:
|
else:
|
||||||
self.wechat_instance.send_text(self.edit_wxid.getText(), self.edit_content.getText())
|
self.wechat_instance.send_text(self.edit_wxid.getText(), self.edit_content.getText())
|
||||||
|
|
||||||
|
@RunUiThread()
|
||||||
def on_recv_message(self, wechat, message):
|
def on_recv_message(self, wechat, message):
|
||||||
text = self.edit_log.getText()
|
text = self.edit_log.getText()
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
@ -184,7 +201,3 @@ if __name__ == '__main__':
|
||||||
app.exit()
|
app.exit()
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
帮助&支持
|
|
||||||
-------------------------
|
|
||||||
点击链接加入群聊 [PyXCGUI&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI)
|
|
||||||
|
|
102
docs/FAQ.md
Normal file
102
docs/FAQ.md
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
## 1. WeChatVersionNotMatchError异常
|
||||||
|
如果出现`ntchat.exception.WeChatVersionNotMatchError`异常, 请确认是否安装github上指定的微信版本,如果确认已经安装,还是报错,可以在代码中添加以下代码,跳过微信版本检测
|
||||||
|
```python
|
||||||
|
import ntchat
|
||||||
|
ntchat.set_wechat_exe_path(wechat_version='3.6.0.18')
|
||||||
|
```
|
||||||
|
如果还是无法正常使用,但确认已经安装过了3.6.0.18版本可以如下设置
|
||||||
|
```python
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
# wechat_exe_path设置成自己3.6.0.18版本的微信的安装路径
|
||||||
|
ntchat.set_wechat_exe_path(
|
||||||
|
wechat_exe_path=r"C:\Program Files (x86)\Tencent\WeChat\WeChat.exe",
|
||||||
|
wechat_version="3.6.0.18")
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以使用注册表修复这个问题,将下面内容保存成WeChatFix.reg, 并双击运行, 如果安装时有修改安装路径,需要修改下面的InstallPath为自己设定的安装路径
|
||||||
|
```editorconfig
|
||||||
|
Windows Registry Editor Version 5.00
|
||||||
|
|
||||||
|
[HKEY_CURRENT_USER\SOFTWARE\Tencent\WeChat]
|
||||||
|
"Version"=dword:63060012
|
||||||
|
"InstallPath"="C:\Program Files (x86)\Tencent\WeChat"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. `ImportError: cannot import name 'wcprobe' from 'ntchat.wc'`
|
||||||
|
|
||||||
|
出现在这个错误的原因是因为你在github下载的源码目录中运行程序,因为wcprobe是根据python版本自动编译生成的,所以源码目录中没有这个文件.
|
||||||
|
|
||||||
|
你需要将要`运行的例子文件移动到非源码目录下`,再去运行或打包
|
||||||
|
|
||||||
|
## 3. 如何多开
|
||||||
|
|
||||||
|
新建多个ntchat.WeChat实例,然后调用open方法:
|
||||||
|
```python
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
# 多开3个微信
|
||||||
|
for i in range(3):
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
wechat.open(smart=False)
|
||||||
|
```
|
||||||
|
更完善的多实例管理查看[fastapi_example例子](./fastapi_example)
|
||||||
|
|
||||||
|
## 4. 如何监听输出所有的消息
|
||||||
|
```python
|
||||||
|
# 注册监听所有消息回调
|
||||||
|
@wechat.msg_register(ntchat.MT_ALL)
|
||||||
|
def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
print("########################")
|
||||||
|
print(message)
|
||||||
|
```
|
||||||
|
完全例子查看[examples/msg_register_all.py](../examples/msg_register_all.py)
|
||||||
|
|
||||||
|
## 5. 如何关闭NtChat的日志
|
||||||
|
|
||||||
|
`os.environ['NTCHAT_LOG'] = "ERROR"` 要在`import ntchat`前执行
|
||||||
|
```python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
os.environ['NTCHAT_LOG'] = "ERROR"
|
||||||
|
|
||||||
|
import ntchat
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 如何正常的关闭Cmd窗口
|
||||||
|
|
||||||
|
先使用`pip install pywin32` 安装pywin32模块, 然后在代码中添加以下代码, 完整例子查看[examples/cmd_close_event.py](../examples/cmd_close_event.py)
|
||||||
|
```python
|
||||||
|
import sys
|
||||||
|
import ntchat
|
||||||
|
import win32api
|
||||||
|
|
||||||
|
def on_exit(sig, func=None):
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
# 当关闭cmd窗口时
|
||||||
|
win32api.SetConsoleCtrlHandler(on_exit, True)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 7. pyinstaller打包exe
|
||||||
|
使用pyinstaller打包NtChat项目,需要添加`--collect-data=ntchat`选项
|
||||||
|
|
||||||
|
打包成单个exe程序
|
||||||
|
```bash
|
||||||
|
pyinstaller -F --collect-data=ntchat main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
将所有的依赖文件打包到一个目录中
|
||||||
|
```bash
|
||||||
|
pyinstaller -y --collect-data=ntchat main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
打包fastapi_example示例,需要添加`--paths=. --collect-data=ntchat`
|
||||||
|
```bash
|
||||||
|
pyinstaller -F --paths=. --collect-data=ntchat main.py
|
||||||
|
```
|
|
@ -22,7 +22,11 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
scene = dom.documentElement.getAttribute("scene")
|
scene = dom.documentElement.getAttribute("scene")
|
||||||
|
|
||||||
# 自动同意好友申请
|
# 自动同意好友申请
|
||||||
wechat_instance.accept_friend_request(encryptusername, ticket, int(scene))
|
ret = wechat_instance.accept_friend_request(encryptusername, ticket, int(scene))
|
||||||
|
|
||||||
|
if ret:
|
||||||
|
# 通过后向他发条消息
|
||||||
|
wechat_instance.send_text(to_wxid=ret["userName"], content="你好!!!!!")
|
||||||
|
|
||||||
|
|
||||||
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
|
43
examples/bomber.py
Normal file
43
examples/bomber.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
os.environ['NTCHAT_LOG'] = "ERROR"
|
||||||
|
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
print("正在登录微信")
|
||||||
|
wechat.wait_login()
|
||||||
|
|
||||||
|
peer_wxid = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
contact_remark = input("请输入想发送的联系人备注: ")
|
||||||
|
contacts = wechat.search_contacts(remark=contact_remark)
|
||||||
|
if not contacts:
|
||||||
|
print(f"没有搜索到备注是{contact_remark}的联系人")
|
||||||
|
else:
|
||||||
|
print(f"搜索到{len(contacts)}个联系人: ")
|
||||||
|
print("0. 重新选择")
|
||||||
|
for i, contact in enumerate(contacts):
|
||||||
|
print(f"{i+1}. 昵称: {contact['nickname']}, 备注: {contact['remark']}")
|
||||||
|
seq = int(input("输入上面编号进行选择: "))
|
||||||
|
if seq != 0:
|
||||||
|
peer_wxid = contacts[seq-1]["wxid"]
|
||||||
|
break
|
||||||
|
|
||||||
|
content = input("请输入发送的内容: ")
|
||||||
|
number = int(input("请输入发送的次数: "))
|
||||||
|
|
||||||
|
for i in range(1, number+1):
|
||||||
|
time.sleep(0.1)
|
||||||
|
print("正在发送第%d遍" % i)
|
||||||
|
wechat.send_text(to_wxid=peer_wxid, content=content)
|
||||||
|
|
||||||
|
|
||||||
|
ntchat.exit_()
|
||||||
|
|
||||||
|
|
||||||
|
|
34
examples/close_log.py
Normal file
34
examples/close_log.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
os.environ['NTCHAT_LOG'] = "ERROR"
|
||||||
|
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
|
||||||
|
# 注册消息回调
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_TEXT_MSG)
|
||||||
|
def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
room_wxid = data["room_wxid"]
|
||||||
|
|
||||||
|
# 判断消息不是自己发的并且不是群消息时,回复对方
|
||||||
|
if from_wxid != self_wxid and not room_wxid:
|
||||||
|
wechat_instance.send_text(to_wxid=from_wxid, content=f"你发送的消息是: {data['msg']}")
|
||||||
|
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
47
examples/cmd_close_event.py
Normal file
47
examples/cmd_close_event.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
try:
|
||||||
|
import win32api
|
||||||
|
except ImportError:
|
||||||
|
print("Error: this example require pywin32, use `pip install pywin32` install")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
|
||||||
|
# 注册消息回调
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_TEXT_MSG)
|
||||||
|
def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
|
||||||
|
# 判断消息不是自己发的,并回复对方
|
||||||
|
if from_wxid != self_wxid:
|
||||||
|
wechat_instance.send_text(to_wxid=from_wxid, content=f"你发送的消息是: {data['msg']}")
|
||||||
|
|
||||||
|
|
||||||
|
def exit_application():
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
def on_exit(sig, func=None):
|
||||||
|
exit_application()
|
||||||
|
|
||||||
|
|
||||||
|
# 当关闭cmd窗口时
|
||||||
|
win32api.SetConsoleCtrlHandler(on_exit, True)
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
# 当Ctrl+C结束程序时
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit_application()
|
35
examples/get_publics.py
Normal file
35
examples/get_publics.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
|
||||||
|
def version_tuple(v):
|
||||||
|
return tuple(map(int, (v.split("."))))
|
||||||
|
|
||||||
|
|
||||||
|
if version_tuple(ntchat.__version__) < version_tuple('0.1.8'):
|
||||||
|
print("error: ntchat version required 0.1.8, use `pip install -U ntchat` to upgrade")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
# 等待登录
|
||||||
|
wechat.wait_login()
|
||||||
|
|
||||||
|
# 获取群列表并输出
|
||||||
|
rooms = wechat.get_publics()
|
||||||
|
|
||||||
|
print("公众号列表: ")
|
||||||
|
print(rooms)
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
25
examples/msg_register_all.py
Normal file
25
examples/msg_register_all.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
|
||||||
|
# 注册监听所有消息回调
|
||||||
|
@wechat.msg_register(ntchat.MT_ALL)
|
||||||
|
def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
print("########################")
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
38
examples/quit_event.py
Normal file
38
examples/quit_event.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
|
||||||
|
def version_tuple(v):
|
||||||
|
return tuple(map(int, (v.split("."))))
|
||||||
|
|
||||||
|
|
||||||
|
if version_tuple(ntchat.__version__) < version_tuple('0.1.4'):
|
||||||
|
print("error: ntchat version required 0.1.4, use `pip install -U ntchat` to upgrade")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
global_quit_flag = False
|
||||||
|
|
||||||
|
|
||||||
|
# 微信进程关闭通知
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_WECHAT_QUIT_MSG)
|
||||||
|
def on_wechat_quit(wechat_instace):
|
||||||
|
print("###################")
|
||||||
|
global global_quit_flag
|
||||||
|
global_quit_flag = True
|
||||||
|
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
while True:
|
||||||
|
if global_quit_flag:
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
58
examples/schedule_send_text.py
Normal file
58
examples/schedule_send_text.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
from datetime import datetime
|
||||||
|
try:
|
||||||
|
import schedule
|
||||||
|
except ImportError:
|
||||||
|
print("Error: this example require schedule module, use `pip install schedule` install")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# 创建微信实例
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
|
||||||
|
# 发送文本消息任务
|
||||||
|
def send_text_job():
|
||||||
|
if not wechat.login_status:
|
||||||
|
return
|
||||||
|
human_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
wechat.send_text(to_wxid="filehelper", content=f"[NtChat] {human_time}")
|
||||||
|
|
||||||
|
|
||||||
|
# 设置调度的参数,这里是每5秒执行一次
|
||||||
|
schedule.every(5).seconds.do(send_text_job)
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
# 每小时执行
|
||||||
|
schedule.every().hour.do(job)
|
||||||
|
|
||||||
|
# 每天12:25执行
|
||||||
|
schedule.every().day.at("12:25").do(job)
|
||||||
|
|
||||||
|
# 每2到5分钟时执行
|
||||||
|
schedule.every(5).to(10).minutes.do(job)
|
||||||
|
|
||||||
|
# 每星期4的19:15执行
|
||||||
|
schedule.every().thursday.at("19:15").do(job)
|
||||||
|
|
||||||
|
# 每第17分钟时就执行
|
||||||
|
schedule.every().minute.at(":17").do(job)
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
46
examples/search_contacts.py
Normal file
46
examples/search_contacts.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
|
||||||
|
def version_tuple(v):
|
||||||
|
return tuple(map(int, (v.split("."))))
|
||||||
|
|
||||||
|
|
||||||
|
if version_tuple(ntchat.__version__) < version_tuple('0.1.7'):
|
||||||
|
print("error: ntchat version required 0.1.7, use `pip install -U ntchat` to upgrade")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
# 等待登录
|
||||||
|
wechat.wait_login()
|
||||||
|
|
||||||
|
# 根据wxid模糊查询查询联系人
|
||||||
|
contacts = wechat.search_contacts(wxid="wxid_")
|
||||||
|
print(contacts)
|
||||||
|
|
||||||
|
# 根据微信号模糊查询联系人
|
||||||
|
# contacts = wechat.search_contacts(account="")
|
||||||
|
|
||||||
|
|
||||||
|
# 根据昵称模糊查询联系人, 如昵称包含`小`的联系人
|
||||||
|
contacts = wechat.search_contacts(nickname="小")
|
||||||
|
print(contacts)
|
||||||
|
|
||||||
|
# 根据备注查询联系人
|
||||||
|
contacts = wechat.search_contacts(remark="备注")
|
||||||
|
print(contacts)
|
||||||
|
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
42
examples/send_room_at_msg.py
Normal file
42
examples/send_room_at_msg.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
# 等待登录
|
||||||
|
wechat.wait_login()
|
||||||
|
|
||||||
|
'''
|
||||||
|
test,你好{$@},你好{$@}.早上好
|
||||||
|
|
||||||
|
发送内容中{$@}占位符说明:
|
||||||
|
|
||||||
|
文本消息的content的内容中设置占位字符串 {$@},这些字符的位置就是最终的@符号所在的位置
|
||||||
|
假设这两个被@的微信号的群昵称分别为aa,bb
|
||||||
|
则实际发送的内容为 "test,你好@ aa,你好@ bb.早上好"(占位符被替换了)
|
||||||
|
|
||||||
|
占位字符串的数量必须和at_list中的微信数量相等.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 下面是@两个人的发送例子,room_wxid, at_list需要自己替换
|
||||||
|
wechat.send_room_at_msg(to_wxid="xxxxxx@chatroom",
|
||||||
|
content="测试, 你好{$@},你好{$@}",
|
||||||
|
at_list=['wxid_xxxxxxxx', 'wxid_xxxxxxxxx'])
|
||||||
|
|
||||||
|
|
||||||
|
# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import xcgui
|
import xcgui
|
||||||
import ntchat
|
import ntchat
|
||||||
from xcgui import XApp, XWindow
|
from xcgui import XApp, XWindow, RunUiThread
|
||||||
|
|
||||||
|
|
||||||
class NtChatWindow(XWindow):
|
class NtChatWindow(XWindow):
|
||||||
|
@ -38,6 +38,7 @@ class NtChatWindow(XWindow):
|
||||||
else:
|
else:
|
||||||
self.wechat_instance.send_text(self.edit_wxid.getText(), self.edit_content.getText())
|
self.wechat_instance.send_text(self.edit_wxid.getText(), self.edit_content.getText())
|
||||||
|
|
||||||
|
@RunUiThread()
|
||||||
def on_recv_message(self, wechat, message):
|
def on_recv_message(self, wechat, message):
|
||||||
text = self.edit_log.getText()
|
text = self.edit_log.getText()
|
||||||
text += "\n"
|
text += "\n"
|
||||||
|
|
18
examples/show_login_qrcode.py
Normal file
18
examples/show_login_qrcode.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
|
||||||
|
def version_tuple(v):
|
||||||
|
return tuple(map(int, (v.split("."))))
|
||||||
|
|
||||||
|
|
||||||
|
if version_tuple(ntchat.__version__) < version_tuple('0.1.15'):
|
||||||
|
print("error: ntchat version required 0.1.15, use `pip install -U ntchat` to upgrade")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 打开一个新的微信,并显示二维码界面
|
||||||
|
wechat.open(smart=False, show_login_qrcode=True)
|
119
examples/transmit.py
Normal file
119
examples/transmit.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from email import message
|
||||||
|
from email.mime import image
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
import time
|
||||||
|
import ntchat
|
||||||
|
import re
|
||||||
|
|
||||||
|
# 聊天记录通知
|
||||||
|
MT_RECV_CHAT_RECORDS_MSG = 11061
|
||||||
|
|
||||||
|
wechat = ntchat.WeChat()
|
||||||
|
|
||||||
|
# 要监听的wxids,可以通过获取contact接口获取wxid,也可以开启后从debug信息中看出来
|
||||||
|
from_wxids = ["xxxxx", "xxxxxx"]
|
||||||
|
# 要转发的目标群
|
||||||
|
target_wxids = ["xxxxxxxx@chatroom"]
|
||||||
|
# 检查文件等待时长,单位s
|
||||||
|
wait_limit = 10
|
||||||
|
|
||||||
|
# 打开pc微信, smart: 是否管理已经登录的微信
|
||||||
|
wechat.open(smart=True)
|
||||||
|
|
||||||
|
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_TEXT_MSG)
|
||||||
|
def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
|
||||||
|
# 判断消息不是自己发的,且来自于想要转发的用户列表,并发给target用户
|
||||||
|
if from_wxid != self_wxid and from_wxid in from_wxids:
|
||||||
|
for target_wxid in target_wxids:
|
||||||
|
wechat_instance.send_text(to_wxid=target_wxid,
|
||||||
|
content=f"{data['msg']}")
|
||||||
|
|
||||||
|
|
||||||
|
# 等待file_path的文件被下载,超过等待次数后返回true
|
||||||
|
def wait_for_file(file_path) -> bool:
|
||||||
|
cnt = 0
|
||||||
|
while not os.path.exists(file_path):
|
||||||
|
time.sleep(1)
|
||||||
|
cnt = cnt + 1
|
||||||
|
if cnt > wait_limit:
|
||||||
|
print(
|
||||||
|
f"wait for {wait_limit} second, but file cannot be downloaded, forgive."
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_IMAGE_MSG)
|
||||||
|
def on_recv_img_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
img_path = data["image"]
|
||||||
|
# img_path = "D:\\a.png"
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
|
||||||
|
# 判断消息不是自己发的,且来自于想要转发的用户列表,并发给target用户
|
||||||
|
if from_wxid != self_wxid and from_wxid in from_wxids:
|
||||||
|
if wait_for_file(file_path=img_path):
|
||||||
|
for target_wxid in target_wxids:
|
||||||
|
wechat_instance.send_image(to_wxid=target_wxid, file_path=img_path)
|
||||||
|
|
||||||
|
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_FILE_MSG)
|
||||||
|
def on_recv_img_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
file_path = data["file"]
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
|
||||||
|
# 判断消息不是自己发的,且来自于想要转发的用户列表,并发给target用户
|
||||||
|
if from_wxid != self_wxid and from_wxid in from_wxids:
|
||||||
|
if wait_for_file(file_path=file_path):
|
||||||
|
for target_wxid in target_wxids:
|
||||||
|
wechat_instance.send_file(to_wxid=target_wxid, file_path=file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def update_wxid_in_xml(xml, from_wxid, target_wxid):
|
||||||
|
patten = re.compile(from_wxid)
|
||||||
|
return patten.sub(target_wxid, xml)
|
||||||
|
|
||||||
|
|
||||||
|
@wechat.msg_register(MT_RECV_CHAT_RECORDS_MSG)
|
||||||
|
def on_recv_chat_record_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
raw_msg = data["raw_msg"]
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
xml = update_wxid_in_xml(raw_msg, from_wxid, self_wxid)
|
||||||
|
# 判断消息不是自己发的,且来自于想要转发的用户列表,并发给target用户
|
||||||
|
if from_wxid != self_wxid and from_wxid in from_wxids:
|
||||||
|
for target_wxid in target_wxids:
|
||||||
|
wechat_instance.send_xml(to_wxid=target_wxid, xml=xml)
|
||||||
|
|
||||||
|
|
||||||
|
@wechat.msg_register(ntchat.MT_RECV_LINK_MSG)
|
||||||
|
def on_recv_link_msg(wechat_instance: ntchat.WeChat, message):
|
||||||
|
data = message["data"]
|
||||||
|
from_wxid = data["from_wxid"]
|
||||||
|
raw_msg = data["raw_msg"]
|
||||||
|
# xml中的fromusername改成自己的wxid 再发
|
||||||
|
self_wxid = wechat_instance.get_login_info()["wxid"]
|
||||||
|
xml = update_wxid_in_xml(raw_msg, from_wxid, self_wxid)
|
||||||
|
# 判断消息不是自己发的,且来自于想要转发的用户列表,并发给target用户
|
||||||
|
if from_wxid != self_wxid and from_wxid in from_wxids:
|
||||||
|
for target_wxid in target_wxids:
|
||||||
|
wechat_instance.send_xml(to_wxid=target_wxid, xml=xml)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
ntchat.exit_()
|
||||||
|
sys.exit()
|
26
fastapi_example/README.md
Normal file
26
fastapi_example/README.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
## NtChat fastapi完整示例
|
||||||
|
|
||||||
|
通过fastapi的swagger在线文档可以很方便的管理NtChat接口
|
||||||
|

|
||||||
|
|
||||||
|
## 安装依赖
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行例子
|
||||||
|
```bash
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 访问api在线文档
|
||||||
|
[http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)
|
||||||
|
|
||||||
|
|
||||||
|
## 如何调用
|
||||||
|
|
||||||
|
可以使用requests库去访问接口
|
||||||
|
|
||||||
|
/client/create 是创建一个微信的实例,返回guid,标识实例的id, 后面所有的接口都要用到
|
||||||
|
|
||||||
|
/client/open 是打开并管理上微信实例
|
0
fastapi_example/__init__.py
Normal file
0
fastapi_example/__init__.py
Normal file
24
fastapi_example/down.py
Normal file
24
fastapi_example/down.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import os.path
|
||||||
|
import requests
|
||||||
|
from xdg import get_download_dir
|
||||||
|
from models import SendMediaReqModel
|
||||||
|
from ntchat.utils import generate_guid
|
||||||
|
|
||||||
|
|
||||||
|
def new_download_file():
|
||||||
|
while True:
|
||||||
|
path = os.path.join(get_download_dir(), generate_guid("temp"))
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_path(model: SendMediaReqModel):
|
||||||
|
if os.path.isfile(model.file_path):
|
||||||
|
return model.file_path
|
||||||
|
if not model.url:
|
||||||
|
return None
|
||||||
|
data = requests.get(model.url).content
|
||||||
|
temp_file = new_download_file()
|
||||||
|
with open(temp_file, 'wb') as fp:
|
||||||
|
fp.write(data)
|
||||||
|
return temp_file
|
9
fastapi_example/exception.py
Normal file
9
fastapi_example/exception.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class ClientNotExists(Exception):
|
||||||
|
guid = ""
|
||||||
|
|
||||||
|
def __init__(self, guid):
|
||||||
|
self.guid = guid
|
||||||
|
|
||||||
|
|
||||||
|
class MediaNotExistsError(Exception):
|
||||||
|
pass
|
285
fastapi_example/main.py
Normal file
285
fastapi_example/main.py
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import uvicorn
|
||||||
|
import threading
|
||||||
|
from functools import wraps
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from mgr import ClientManager
|
||||||
|
from down import get_local_path
|
||||||
|
from exception import MediaNotExistsError, ClientNotExists
|
||||||
|
import models
|
||||||
|
import ntchat
|
||||||
|
|
||||||
|
|
||||||
|
def response_json(status=0, data=None, msg=""):
|
||||||
|
return {
|
||||||
|
"status": status,
|
||||||
|
"data": {} if data is None else data,
|
||||||
|
"msg": msg
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class catch_exception:
|
||||||
|
def __call__(self, f):
|
||||||
|
@wraps(f)
|
||||||
|
async def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return await f(*args, **kwargs)
|
||||||
|
except ntchat.WeChatNotLoginError:
|
||||||
|
return response_json(msg="wechat instance not login")
|
||||||
|
except ntchat.WeChatBindError:
|
||||||
|
return response_json(msg="wechat bind error")
|
||||||
|
except ntchat.WeChatVersionNotMatchError:
|
||||||
|
return response_json(msg="wechat version not match, install require wechat version")
|
||||||
|
except MediaNotExistsError:
|
||||||
|
return response_json(msg="file_path or url error")
|
||||||
|
except ClientNotExists as e:
|
||||||
|
return response_json(msg="client not exists, guid: %s" % e.guid)
|
||||||
|
except Exception as e:
|
||||||
|
return response_json(msg=str(e))
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
client_mgr = ClientManager()
|
||||||
|
app = FastAPI(title="NtChat fastapi完整示例",
|
||||||
|
description="NtChat项目地址: https://github.com/smallevilbeast/ntchat")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/client/create", summary="创建实例", tags=["Client"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def client_create():
|
||||||
|
guid = client_mgr.create_client()
|
||||||
|
return response_json(1, {"guid": guid})
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/client/open", summary="打开微信", tags=["Client"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def client_open(model: models.ClientOpenReqModel):
|
||||||
|
client = client_mgr.get_client(model.guid)
|
||||||
|
ret = client.open(model.smart, model.show_login_qrcode)
|
||||||
|
|
||||||
|
# 当show_login_qrcode=True时, 打开微信时会显示二维码界面
|
||||||
|
if model.show_login_qrcode:
|
||||||
|
client.qrcode_event = threading.Event()
|
||||||
|
client.qrcode_event.wait(timeout=10)
|
||||||
|
return response_json(1 if ret else 0, {'qrcode': client.qrcode})
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/global/set_callback_url", summary="设置接收通知地址", tags=["Global"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def client_set_callback_url(model: models.CallbackUrlReqModel):
|
||||||
|
client_mgr.callback_url = model.callback_url
|
||||||
|
return response_json(1)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/user/get_profile", summary="获取自己的信息", tags=["User"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def user_get_profile(model: models.ClientReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).get_self_info()
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/contact/get_contacts", summary="获取联系人列表", tags=["Contact"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def get_contacts(model: models.ClientReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).get_contacts()
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/contact/get_contact_detail", summary="获取指定联系人详细信息", tags=["Contact"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def get_contact_detail(model: models.ContactDetailReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).get_contact_detail(model.wxid)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/contact/modify_remark", summary="修改联系人备注", tags=["Contact"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_gif(model: models.ModifyFriendRemarkReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).modify_friend_remark(model.wxid, model.remark)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/get_rooms", summary="获取群列表", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def get_rooms(model: models.ClientReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).get_rooms()
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/get_name_name", summary="获取群名称", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def get_rooms(model: models.GetRoomNameReqModel):
|
||||||
|
name = client_mgr.get_client(model.guid).get_room_name(model.room_wxid)
|
||||||
|
data = {
|
||||||
|
"name": name
|
||||||
|
}
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/get_room_members", summary="获取群成员列表", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def get_room_members(model: models.GetRoomMembersReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).get_room_members(model.room_wxid)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/create_room", summary="创建群", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def create_room(model: models.CreateRoomReqModel):
|
||||||
|
ret = client_mgr.get_client(model.guid).create_room(model.member_list)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/add_room_member", summary="添加好友入群", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def add_room_member(model: models.RoomMembersReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).add_room_member(model.room_wxid, model.member_list)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/invite_room_member", summary="邀请好友入群", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def invite_room_member(model: models.RoomMembersReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).invite_room_member(model.room_wxid, model.member_list)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/del_room_member", summary="删除群成员", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def del_room_member(model: models.RoomMembersReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).del_room_member(model.room_wxid, model.member_list)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/add_room_friend", summary="添加群成员为好友", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def add_room_friend(model: models.AddRoomFriendReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).add_room_friend(model.room_wxid,
|
||||||
|
model.wxid,
|
||||||
|
model.verify)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/modify_name", summary="修改群名", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def add_room_friend(model: models.ModifyRoomNameReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).modify_room_name(model.room_wxid,
|
||||||
|
model.name)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/room/quit_room", summary="退出群", tags=["Room"],
|
||||||
|
response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def quit_room(model: models.RoomReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).quit_room(model.room_wxid)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_text", summary="发送文本消息", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def msg_send_text(model: models.SendTextReqModel):
|
||||||
|
ret = client_mgr.get_client(model.guid).send_text(model.to_wxid, model.content)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_room_at", summary="发送群@消息", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_room_at(model: models.SendRoomAtReqModel):
|
||||||
|
ret = client_mgr.get_client(model.guid).send_room_at_msg(model.to_wxid,
|
||||||
|
model.content,
|
||||||
|
model.at_list)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_card", summary="发送名片", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_card(model: models.SendCardReqModel):
|
||||||
|
ret = client_mgr.get_client(model.guid).send_card(model.to_wxid,
|
||||||
|
model.card_wxid)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_link_card", summary="发送链接卡片消息", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_link_card(model: models.SendLinkCardReqModel):
|
||||||
|
ret = client_mgr.get_client(model.guid).send_link_card(model.to_wxid,
|
||||||
|
model.title,
|
||||||
|
model.desc,
|
||||||
|
model.url,
|
||||||
|
model.image_url)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_image", summary="发送图片", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_image(model: models.SendMediaReqModel):
|
||||||
|
file_path = get_local_path(model)
|
||||||
|
if file_path is None:
|
||||||
|
raise MediaNotExistsError()
|
||||||
|
ret = client_mgr.get_client(model.guid).send_image(model.to_wxid, file_path)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_file", summary="发送文件", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_file(model: models.SendMediaReqModel):
|
||||||
|
file_path = get_local_path(model)
|
||||||
|
if file_path is None:
|
||||||
|
raise MediaNotExistsError()
|
||||||
|
ret = client_mgr.get_client(model.guid).send_file(model.to_wxid, file_path)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_video", summary="发送视频", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_video(model: models.SendMediaReqModel):
|
||||||
|
file_path = get_local_path(model)
|
||||||
|
if file_path is None:
|
||||||
|
raise MediaNotExistsError()
|
||||||
|
ret = client_mgr.get_client(model.guid).send_video(model.to_wxid, file_path)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_gif", summary="发送GIF", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_gif(model: models.SendMediaReqModel):
|
||||||
|
file_path = get_local_path(model)
|
||||||
|
if file_path is None:
|
||||||
|
raise MediaNotExistsError()
|
||||||
|
ret = client_mgr.get_client(model.guid).send_gif(model.to_wxid, file_path)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_xml", summary="发送XML原始消息", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_gif(model: models.SendXmlReqModel):
|
||||||
|
ret = client_mgr.get_client(model.guid).send_xml(model.to_wxid, model.xml)
|
||||||
|
return response_json(1 if ret else 0)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/msg/send_pat", summary="发送拍一拍", tags=["Msg"], response_model=models.ResponseModel)
|
||||||
|
@catch_exception()
|
||||||
|
async def send_gif(model: models.SendPatReqModel):
|
||||||
|
data = client_mgr.get_client(model.guid).send_pat(model.room_wxid, model.patted_wxid)
|
||||||
|
return response_json(1, data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run(app=app, host='0.0.0.0', port=8000)
|
69
fastapi_example/mgr.py
Normal file
69
fastapi_example/mgr.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import ntchat
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
from typing import Dict, Union
|
||||||
|
from ntchat.utils.singleton import Singleton
|
||||||
|
from utils import generate_guid
|
||||||
|
from exception import ClientNotExists
|
||||||
|
|
||||||
|
|
||||||
|
class ClientWeChat(ntchat.WeChat):
|
||||||
|
guid: str = ""
|
||||||
|
qrcode_event: threading.Event = None
|
||||||
|
qrcode: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ClientManager(metaclass=Singleton):
|
||||||
|
__client_map: Dict[str, ntchat.WeChat] = {}
|
||||||
|
callback_url: str = ""
|
||||||
|
|
||||||
|
def new_guid(self):
|
||||||
|
"""
|
||||||
|
生成新的guid
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
guid = generate_guid("wechat")
|
||||||
|
if guid not in self.__client_map:
|
||||||
|
return guid
|
||||||
|
|
||||||
|
def create_client(self):
|
||||||
|
guid = self.new_guid()
|
||||||
|
wechat = ClientWeChat()
|
||||||
|
wechat.guid = guid
|
||||||
|
self.__client_map[guid] = wechat
|
||||||
|
|
||||||
|
# 注册回调
|
||||||
|
wechat.on(ntchat.MT_ALL, self.__on_callback)
|
||||||
|
wechat.on(ntchat.MT_RECV_WECHAT_QUIT_MSG, self.__on_quit_callback)
|
||||||
|
return guid
|
||||||
|
|
||||||
|
def get_client(self, guid: str) -> ClientWeChat:
|
||||||
|
client = self.__client_map.get(guid, None)
|
||||||
|
if client is None:
|
||||||
|
raise ClientNotExists(guid)
|
||||||
|
return client
|
||||||
|
|
||||||
|
def remove_client(self, guid):
|
||||||
|
if guid in self.__client_map:
|
||||||
|
del self.__client_map[guid]
|
||||||
|
|
||||||
|
def __on_callback(self, wechat: ClientWeChat, message: dict):
|
||||||
|
|
||||||
|
# 通知二维码显示
|
||||||
|
msg_type = message['type']
|
||||||
|
if msg_type == ntchat.MT_RECV_LOGIN_QRCODE_MSG and wechat.qrcode_event:
|
||||||
|
wechat.qrcode = message["data"]["code"]
|
||||||
|
wechat.qrcode_event.set()
|
||||||
|
|
||||||
|
if not self.callback_url:
|
||||||
|
return
|
||||||
|
|
||||||
|
client_message = {
|
||||||
|
"guid": wechat.guid,
|
||||||
|
"message": message
|
||||||
|
}
|
||||||
|
requests.post(self.callback_url, json=client_message)
|
||||||
|
|
||||||
|
def __on_quit_callback(self, wechat):
|
||||||
|
self.__on_callback(wechat, {"type": ntchat.MT_RECV_WECHAT_QUIT_MSG, "data": {}})
|
157
fastapi_example/models.py
Normal file
157
fastapi_example/models.py
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
from typing import Optional, List, Any, Union, Dict
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ClientReqModel(BaseModel):
|
||||||
|
guid: str
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseModel(BaseModel):
|
||||||
|
status: int
|
||||||
|
msg: Optional[str] = ""
|
||||||
|
data: Optional[Any] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ClientOpenReqModel(ClientReqModel):
|
||||||
|
smart: Optional[bool] = True
|
||||||
|
show_login_qrcode: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackUrlReqModel(BaseModel):
|
||||||
|
callback_url: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfileModel(BaseModel):
|
||||||
|
wxid: str
|
||||||
|
nickname: str
|
||||||
|
account: str
|
||||||
|
avatar: str
|
||||||
|
|
||||||
|
|
||||||
|
class ContactModel(BaseModel):
|
||||||
|
account: str
|
||||||
|
avatar: str
|
||||||
|
city: str
|
||||||
|
country: str
|
||||||
|
nickname: str
|
||||||
|
province: str
|
||||||
|
remark: str
|
||||||
|
sex: int
|
||||||
|
wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class ContactDetailReqModel(ClientReqModel):
|
||||||
|
wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class ContactDetailModel(BaseModel):
|
||||||
|
account: str
|
||||||
|
avatar: str
|
||||||
|
city: str
|
||||||
|
country: str
|
||||||
|
nickname: str
|
||||||
|
province: str
|
||||||
|
remark: str
|
||||||
|
sex: int
|
||||||
|
wxid: str
|
||||||
|
signature: str
|
||||||
|
small_avatar: str
|
||||||
|
sns_pic: str
|
||||||
|
source_type: int
|
||||||
|
status: int
|
||||||
|
v1: str
|
||||||
|
v2: str
|
||||||
|
|
||||||
|
|
||||||
|
class AcceptFriendReqModel(ClientReqModel):
|
||||||
|
encryptusername: str
|
||||||
|
ticket: str
|
||||||
|
scene: int
|
||||||
|
|
||||||
|
|
||||||
|
class RoomModel(BaseModel):
|
||||||
|
wxid: str
|
||||||
|
nickname: str
|
||||||
|
avatar: str
|
||||||
|
is_manager: int
|
||||||
|
manager_wxid: str
|
||||||
|
total_member: int
|
||||||
|
member_list: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
class RoomMemberModel(ContactModel):
|
||||||
|
display_name: str
|
||||||
|
|
||||||
|
|
||||||
|
class GetRoomMembersReqModel(ClientReqModel):
|
||||||
|
room_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class GetRoomNameReqModel(ClientReqModel):
|
||||||
|
room_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class CreateRoomReqModel(ClientReqModel):
|
||||||
|
member_list: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
class RoomMembersReqModel(CreateRoomReqModel):
|
||||||
|
room_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class AddRoomFriendReqModel(ClientReqModel):
|
||||||
|
room_wxid: str
|
||||||
|
wxid: str
|
||||||
|
verify: str
|
||||||
|
|
||||||
|
|
||||||
|
class RoomReqModel(ClientReqModel):
|
||||||
|
room_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class ModifyRoomNameReqModel(RoomReqModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendMsgReqModel(ClientReqModel):
|
||||||
|
to_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendTextReqModel(SendMsgReqModel):
|
||||||
|
content: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendRoomAtReqModel(SendTextReqModel):
|
||||||
|
at_list: List[str]
|
||||||
|
|
||||||
|
|
||||||
|
class SendCardReqModel(SendMsgReqModel):
|
||||||
|
card_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendLinkCardReqModel(SendMsgReqModel):
|
||||||
|
title: str
|
||||||
|
desc: str
|
||||||
|
url: str
|
||||||
|
image_url: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendMediaReqModel(SendMsgReqModel):
|
||||||
|
file_path: Optional[str] = ""
|
||||||
|
url: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
|
class SendXmlReqModel(SendMsgReqModel):
|
||||||
|
xml: str
|
||||||
|
|
||||||
|
|
||||||
|
class SendPatReqModel(ClientReqModel):
|
||||||
|
room_wxid: str
|
||||||
|
patted_wxid: str
|
||||||
|
|
||||||
|
|
||||||
|
class ModifyFriendRemarkReqModel(ClientReqModel):
|
||||||
|
wxid: str
|
||||||
|
remark: str
|
||||||
|
|
||||||
|
|
4
fastapi_example/requirements.txt
Normal file
4
fastapi_example/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
ntchat
|
||||||
|
fastapi
|
||||||
|
requests
|
||||||
|
uvicorn
|
6
fastapi_example/utils.py
Normal file
6
fastapi_example/utils.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import uuid
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def generate_guid(prefix=''):
|
||||||
|
return str(uuid.uuid3(uuid.NAMESPACE_URL, prefix + str(time.time())))
|
15
fastapi_example/xdg.py
Normal file
15
fastapi_example/xdg.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
def get_exec_dir():
|
||||||
|
return os.path.dirname(sys.argv[0])
|
||||||
|
|
||||||
|
|
||||||
|
def get_download_dir():
|
||||||
|
user_dir = os.path.join(get_exec_dir(), 'download')
|
||||||
|
user_dir = os.path.abspath(user_dir)
|
||||||
|
if not os.path.isdir(user_dir):
|
||||||
|
os.makedirs(user_dir)
|
||||||
|
return user_dir
|
|
@ -16,4 +16,9 @@ def set_wechat_exe_path(wechat_exe_path=None, wechat_version=None):
|
||||||
conf.DEFAULT_WECHAT_VERSION = wechat_version
|
conf.DEFAULT_WECHAT_VERSION = wechat_version
|
||||||
|
|
||||||
|
|
||||||
exit_ = wcprobe.exit
|
def get_install_wechat_version():
|
||||||
|
return wcprobe.get_install_wechat_version()
|
||||||
|
|
||||||
|
|
||||||
|
def exit_():
|
||||||
|
wcprobe.exit()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION = '0.1.3'
|
VERSION = '0.1.17'
|
||||||
|
|
||||||
LOG_LEVEL = "DEBUG"
|
LOG_LEVEL = "DEBUG"
|
||||||
LOG_KEY = 'NTCHAT_LOG'
|
LOG_KEY = 'NTCHAT_LOG'
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# 用于接收所有的通知消息
|
# 用于接收所有的通知消息
|
||||||
MT_ALL = 11000
|
MT_ALL = 11000
|
||||||
|
|
||||||
|
# 微信进程退出通知
|
||||||
|
MT_RECV_WECHAT_QUIT_MSG = 11001
|
||||||
|
|
||||||
# 第个通知消息,此时已经托管上微信
|
# 第个通知消息,此时已经托管上微信
|
||||||
MT_READY_MSG = 11024
|
MT_READY_MSG = 11024
|
||||||
|
|
||||||
|
@ -62,5 +65,22 @@ MT_RECV_OTHER_MSG = 11060
|
||||||
# 未知应用消息通知
|
# 未知应用消息通知
|
||||||
MT_RECV_OTHER_APP_MSG = 11061
|
MT_RECV_OTHER_APP_MSG = 11061
|
||||||
|
|
||||||
|
# 群成员新增通知
|
||||||
|
MT_ROOM_ADD_MEMBER_NOTIFY_MSG = 11098
|
||||||
|
|
||||||
|
# 群成员删除通知
|
||||||
|
MT_ROOM_DEL_MEMBER_NOTIFY_MSG = 11099
|
||||||
|
|
||||||
|
# 通过接口创建群聊的通知
|
||||||
|
MT_ROOM_CREATE_NOTIFY_MSG = 11100
|
||||||
|
|
||||||
|
# 退群或被踢通知
|
||||||
|
MT_ROOM_DEL_NOTIFY_MSG = 11101
|
||||||
|
|
||||||
|
# 联系人新增通知
|
||||||
|
MT_CONTACT_ADD_NOITFY_MSG = 11102
|
||||||
|
|
||||||
|
# 联系人删除通知
|
||||||
|
MT_CONTACT_DEL_NOTIFY_MSG = 11103
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,17 @@ MT_GET_CONTACTS_MSG = 11030
|
||||||
# 获取所有的群
|
# 获取所有的群
|
||||||
MT_GET_ROOMS_MSG = 11031
|
MT_GET_ROOMS_MSG = 11031
|
||||||
|
|
||||||
|
# 获取公众号列表
|
||||||
|
MT_GET_PUBLICS_MSG = 11033
|
||||||
|
|
||||||
# 获取指定的群成员
|
# 获取指定的群成员
|
||||||
MT_GET_ROOM_MEMBERS_MSG = 11032
|
MT_GET_ROOM_MEMBERS_MSG = 11032
|
||||||
|
|
||||||
# 获取指定联系人的详细信息
|
# 获取指定联系人的详细信息
|
||||||
MT_GET_CONTACT_DETAIL_MSG = 11034
|
MT_GET_CONTACT_DETAIL_MSG = 11029
|
||||||
|
|
||||||
|
# 获取指定群的详细信息
|
||||||
|
MT_GET_ROOM_DETAIL_MSG = 11125
|
||||||
|
|
||||||
# 发送文本消息
|
# 发送文本消息
|
||||||
MT_SEND_TEXT_MSG = 11036
|
MT_SEND_TEXT_MSG = 11036
|
||||||
|
@ -37,6 +43,12 @@ MT_SEND_VIDEO_MSG = 11042
|
||||||
# 发送gif消息
|
# 发送gif消息
|
||||||
MT_SEND_GIF_MSG = 11043
|
MT_SEND_GIF_MSG = 11043
|
||||||
|
|
||||||
|
# 发送xml消息
|
||||||
|
MT_SEND_XML_MSG = 11113
|
||||||
|
|
||||||
|
# 修改好友备注
|
||||||
|
MT_MODIFY_FRIEND_REMARK = 11063
|
||||||
|
|
||||||
# 接受新好友请求
|
# 接受新好友请求
|
||||||
MT_ACCEPT_FRIEND_MSG = 11065
|
MT_ACCEPT_FRIEND_MSG = 11065
|
||||||
|
|
||||||
|
@ -63,3 +75,9 @@ MT_QUIT_DEL_ROOM_MSG = 11077
|
||||||
|
|
||||||
# 添加群成员为好友
|
# 添加群成员为好友
|
||||||
MT_ADD_FRIEND_MSG = 11062
|
MT_ADD_FRIEND_MSG = 11062
|
||||||
|
|
||||||
|
# 数据库查询
|
||||||
|
MT_SQL_QUERY_MSG = 11027
|
||||||
|
|
||||||
|
# 拍一拍
|
||||||
|
MT_SEND_PAT_MSG = 11250
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
from ntchat.wc import wcprobe
|
from ntchat.wc import wcprobe, SUPPORT_VERSIONS
|
||||||
from ntchat.utils.xdg import get_helper_file
|
from ntchat.utils.xdg import get_helper_file, is_support_version, has_helper_file
|
||||||
from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError
|
from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError, WeChatRuntimeError
|
||||||
from ntchat.utils.singleton import Singleton
|
from ntchat.utils.singleton import Singleton
|
||||||
from ntchat.const import notify_type
|
from ntchat.const import notify_type
|
||||||
from ntchat.utils.logger import get_logger
|
from ntchat.utils.logger import get_logger
|
||||||
|
@ -31,9 +31,16 @@ class WeChatMgr(metaclass=Singleton):
|
||||||
else:
|
else:
|
||||||
version = wechat_version
|
version = wechat_version
|
||||||
|
|
||||||
|
if not is_support_version(version):
|
||||||
|
raise WeChatVersionNotMatchError(f"ntchat support wechat versions: {','.join(SUPPORT_VERSIONS)}")
|
||||||
|
|
||||||
|
if not has_helper_file():
|
||||||
|
raise WeChatRuntimeError('When using pyinstaller to package exe, you need to add the '
|
||||||
|
'`--collect-data=ntchat` parameter')
|
||||||
|
|
||||||
helper_file = get_helper_file(version)
|
helper_file = get_helper_file(version)
|
||||||
if not os.path.exists(helper_file):
|
if not os.path.exists(helper_file):
|
||||||
raise WeChatVersionNotMatchError()
|
raise WeChatRuntimeError("missing core files")
|
||||||
|
|
||||||
log.info("initialize wechat, version: %s", version)
|
log.info("initialize wechat, version: %s", version)
|
||||||
|
|
||||||
|
@ -49,8 +56,7 @@ class WeChatMgr(metaclass=Singleton):
|
||||||
if client_id not in self.__instance_map:
|
if client_id not in self.__instance_map:
|
||||||
for instance in self.__instance_list:
|
for instance in self.__instance_list:
|
||||||
if instance.pid == pid:
|
if instance.pid == pid:
|
||||||
instance.client_id = client_id
|
instance.bind_client_id(client_id)
|
||||||
instance.status = True
|
|
||||||
self.__instance_map[client_id] = instance
|
self.__instance_map[client_id] = instance
|
||||||
bind_instance = instance
|
bind_instance = instance
|
||||||
break
|
break
|
||||||
|
@ -71,5 +77,4 @@ class WeChatMgr(metaclass=Singleton):
|
||||||
def __on_close(self, client_id):
|
def __on_close(self, client_id):
|
||||||
log.debug("close client_id: %d", client_id)
|
log.debug("close client_id: %d", client_id)
|
||||||
if client_id in self.__instance_map:
|
if client_id in self.__instance_map:
|
||||||
self.__instance_map[client_id].login_status = False
|
self.__instance_map[client_id].on_close()
|
||||||
self.__instance_map[client_id].status = False
|
|
||||||
|
|
|
@ -41,6 +41,17 @@ class ReqData:
|
||||||
return self.__response_message["data"]
|
return self.__response_message["data"]
|
||||||
|
|
||||||
|
|
||||||
|
class RaiseExceptionFunc:
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
self.func(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
log.error('callback error, in function `%s`, error: %s', self.func.__name__, e)
|
||||||
|
|
||||||
|
|
||||||
class WeChat:
|
class WeChat:
|
||||||
client_id: int = 0
|
client_id: int = 0
|
||||||
pid: int = 0
|
pid: int = 0
|
||||||
|
@ -51,23 +62,37 @@ class WeChat:
|
||||||
WeChatMgr().append_instance(self)
|
WeChatMgr().append_instance(self)
|
||||||
self.__wait_login_event = Event()
|
self.__wait_login_event = Event()
|
||||||
self.__req_data_cache = {}
|
self.__req_data_cache = {}
|
||||||
self.__msg_event_emitter = pyee.EventEmitter()
|
self.event_emitter = pyee.EventEmitter()
|
||||||
self.__login_info = {}
|
self.__login_info = {}
|
||||||
|
|
||||||
def on(self, msg_type, f):
|
def on(self, msg_type, f):
|
||||||
return self.__msg_event_emitter.on(str(msg_type), f)
|
|
||||||
|
|
||||||
def msg_register(self, msg_type: Union[int, List[int], Tuple[int]]):
|
|
||||||
if not (isinstance(msg_type, list) or isinstance(msg_type, tuple)):
|
if not (isinstance(msg_type, list) or isinstance(msg_type, tuple)):
|
||||||
msg_type = [msg_type]
|
msg_type = [msg_type]
|
||||||
|
for event in msg_type:
|
||||||
|
self.event_emitter.on(str(event), RaiseExceptionFunc(f))
|
||||||
|
|
||||||
|
def msg_register(self, msg_type: Union[int, List[int], Tuple[int]]):
|
||||||
def wrapper(f):
|
def wrapper(f):
|
||||||
wraps(f)
|
wraps(f)
|
||||||
for event in msg_type:
|
self.on(msg_type, f)
|
||||||
self.on(event, f)
|
|
||||||
return f
|
return f
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
def on_close(self):
|
||||||
|
self.login_status = False
|
||||||
|
self.status = False
|
||||||
|
self.event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self)
|
||||||
|
|
||||||
|
message = {
|
||||||
|
"type": notify_type.MT_RECV_WECHAT_QUIT_MSG,
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
self.event_emitter.emit(str(notify_type.MT_ALL), self, message)
|
||||||
|
|
||||||
|
def bind_client_id(self, client_id):
|
||||||
|
self.status = True
|
||||||
|
self.client_id = client_id
|
||||||
|
|
||||||
def on_recv(self, message):
|
def on_recv(self, message):
|
||||||
log.debug("on recv message: %s", message)
|
log.debug("on recv message: %s", message)
|
||||||
msg_type = message["type"]
|
msg_type = message["type"]
|
||||||
|
@ -86,14 +111,17 @@ class WeChat:
|
||||||
req_data.on_response(message)
|
req_data.on_response(message)
|
||||||
del self.__req_data_cache[extend]
|
del self.__req_data_cache[extend]
|
||||||
else:
|
else:
|
||||||
self.__msg_event_emitter.emit(str(msg_type), self, message)
|
self.event_emitter.emit(str(msg_type), self, message)
|
||||||
self.__msg_event_emitter.emit(str(notify_type.MT_ALL), self, message)
|
self.event_emitter.emit(str(notify_type.MT_ALL), self, message)
|
||||||
|
|
||||||
def wait_login(self, timeout=None):
|
def wait_login(self, timeout=None):
|
||||||
log.info("wait login...")
|
log.info("wait login...")
|
||||||
self.__wait_login_event.wait(timeout)
|
self.__wait_login_event.wait(timeout)
|
||||||
|
|
||||||
def open(self, smart=False):
|
def open(self, smart=False, show_login_qrcode=False):
|
||||||
|
if show_login_qrcode:
|
||||||
|
wcprobe.show_login_qrcode()
|
||||||
|
|
||||||
self.pid = wcprobe.open(smart)
|
self.pid = wcprobe.open(smart)
|
||||||
log.info("open wechat pid: %d", self.pid)
|
log.info("open wechat pid: %d", self.pid)
|
||||||
return self.pid != 0
|
return self.pid != 0
|
||||||
|
@ -137,6 +165,16 @@ class WeChat:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"WeChatInstance(pid: {self.pid}, client_id: {self.client_id})"
|
return f"WeChatInstance(pid: {self.pid}, client_id: {self.client_id})"
|
||||||
|
|
||||||
|
def sql_query(self, sql: str, db: int):
|
||||||
|
"""
|
||||||
|
数据库查询
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"sql": sql,
|
||||||
|
"db": db
|
||||||
|
}
|
||||||
|
return self.__send_sync(send_type.MT_SQL_QUERY_MSG, data)
|
||||||
|
|
||||||
def get_login_info(self):
|
def get_login_info(self):
|
||||||
"""
|
"""
|
||||||
获取登录信息
|
获取登录信息
|
||||||
|
@ -155,18 +193,80 @@ class WeChat:
|
||||||
"""
|
"""
|
||||||
return self.__send_sync(send_type.MT_GET_CONTACTS_MSG)
|
return self.__send_sync(send_type.MT_GET_CONTACTS_MSG)
|
||||||
|
|
||||||
|
def get_publics(self):
|
||||||
|
"""
|
||||||
|
获取关注公众号列表
|
||||||
|
"""
|
||||||
|
return self.__send_sync(send_type.MT_GET_PUBLICS_MSG)
|
||||||
|
|
||||||
def get_contact_detail(self, wxid):
|
def get_contact_detail(self, wxid):
|
||||||
|
"""
|
||||||
|
获取联系人详细信息
|
||||||
|
"""
|
||||||
data = {
|
data = {
|
||||||
"wxid": wxid
|
"wxid": wxid
|
||||||
}
|
}
|
||||||
return self.__send_sync(send_type.MT_GET_CONTACT_DETAIL_MSG, data)
|
return self.__send_sync(send_type.MT_GET_CONTACT_DETAIL_MSG, data)
|
||||||
|
|
||||||
|
def search_contacts(self,
|
||||||
|
wxid: Union[None, str] = None,
|
||||||
|
account: Union[None, str] = None,
|
||||||
|
nickname: Union[None, str] = None,
|
||||||
|
remark: Union[None, str] = None,
|
||||||
|
fuzzy_search: bool = True):
|
||||||
|
"""
|
||||||
|
根据wxid、微信号、昵称和备注模糊搜索联系人
|
||||||
|
"""
|
||||||
|
conds = {}
|
||||||
|
if wxid:
|
||||||
|
conds["username"] = wxid
|
||||||
|
if account:
|
||||||
|
conds["alias"] = account
|
||||||
|
if nickname:
|
||||||
|
conds["nickname"] = nickname
|
||||||
|
if remark:
|
||||||
|
conds["remark"] = remark
|
||||||
|
if not conds:
|
||||||
|
return []
|
||||||
|
|
||||||
|
cond_pairs = []
|
||||||
|
tag = '%' if fuzzy_search else ''
|
||||||
|
for k, v in conds.items():
|
||||||
|
cond_pairs.append(f"{k} like '{tag}{v}{tag}'")
|
||||||
|
|
||||||
|
cond_str = " or ".join(cond_pairs)
|
||||||
|
sql = f"select username from contact where {cond_str}"
|
||||||
|
message = self.sql_query(sql, 1)
|
||||||
|
if not message:
|
||||||
|
return []
|
||||||
|
|
||||||
|
result = message["result"]
|
||||||
|
if not result:
|
||||||
|
return []
|
||||||
|
|
||||||
|
contacts = []
|
||||||
|
for wxid_list in result:
|
||||||
|
if len(wxid_list) > 0:
|
||||||
|
wxid = wxid_list[0]
|
||||||
|
contact = self.get_contact_detail(wxid)
|
||||||
|
contacts.append(contact)
|
||||||
|
return contacts
|
||||||
|
|
||||||
def get_rooms(self):
|
def get_rooms(self):
|
||||||
"""
|
"""
|
||||||
获取群列表
|
获取群列表
|
||||||
"""
|
"""
|
||||||
return self.__send_sync(send_type.MT_GET_ROOMS_MSG)
|
return self.__send_sync(send_type.MT_GET_ROOMS_MSG)
|
||||||
|
|
||||||
|
def get_room_detail(self, room_wxid):
|
||||||
|
"""
|
||||||
|
获取指定群详细信息
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"room_wxid": room_wxid
|
||||||
|
}
|
||||||
|
return self.__send_sync(send_type.MT_GET_ROOM_DETAIL_MSG, data)
|
||||||
|
|
||||||
def get_room_members(self, room_wxid: str):
|
def get_room_members(self, room_wxid: str):
|
||||||
"""
|
"""
|
||||||
获取群成员列表
|
获取群成员列表
|
||||||
|
@ -261,6 +361,27 @@ class WeChat:
|
||||||
}
|
}
|
||||||
return self.__send(send_type.MT_SEND_GIF_MSG, data)
|
return self.__send(send_type.MT_SEND_GIF_MSG, data)
|
||||||
|
|
||||||
|
def send_xml(self, to_wxid, xml, app_type=5):
|
||||||
|
"""
|
||||||
|
发送xml消息
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"to_wxid": to_wxid,
|
||||||
|
"xml": xml,
|
||||||
|
"app_type": app_type
|
||||||
|
}
|
||||||
|
return self.__send(send_type.MT_SEND_XML_MSG, data)
|
||||||
|
|
||||||
|
def send_pat(self, room_wxid: str, patted_wxid: str):
|
||||||
|
"""
|
||||||
|
发送拍一拍
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"room_wxid": room_wxid,
|
||||||
|
"patted_wxid": patted_wxid
|
||||||
|
}
|
||||||
|
return self.__send_sync(send_type.MT_SEND_PAT_MSG, data)
|
||||||
|
|
||||||
def accept_friend_request(self, encryptusername: str, ticket: str, scene: int):
|
def accept_friend_request(self, encryptusername: str, ticket: str, scene: int):
|
||||||
"""
|
"""
|
||||||
同意加好友请求
|
同意加好友请求
|
||||||
|
@ -326,7 +447,7 @@ class WeChat:
|
||||||
"room_wxid": room_wxid,
|
"room_wxid": room_wxid,
|
||||||
"notice": notice
|
"notice": notice
|
||||||
}
|
}
|
||||||
return self.__send_sync(send_type.MT_MOD_ROOM_NAME_MSG, data)
|
return self.__send_sync(send_type.MT_MOD_ROOM_NOTICE_MSG, data)
|
||||||
|
|
||||||
def add_room_friend(self, room_wxid: str, wxid: str, verify: str):
|
def add_room_friend(self, room_wxid: str, wxid: str, verify: str):
|
||||||
"""
|
"""
|
||||||
|
@ -340,3 +461,31 @@ class WeChat:
|
||||||
}
|
}
|
||||||
return self.__send_sync(send_type.MT_ADD_FRIEND_MSG, data)
|
return self.__send_sync(send_type.MT_ADD_FRIEND_MSG, data)
|
||||||
|
|
||||||
|
def quit_room(self, room_wxid: str):
|
||||||
|
"""
|
||||||
|
退出群
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"room_wxid": room_wxid
|
||||||
|
}
|
||||||
|
return self.__send(send_type.MT_QUIT_DEL_ROOM_MSG, data)
|
||||||
|
|
||||||
|
def modify_friend_remark(self, wxid: str, remark: str):
|
||||||
|
"""
|
||||||
|
修改好友备注
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"wxid": wxid,
|
||||||
|
"remark": remark
|
||||||
|
}
|
||||||
|
return self.__send_sync(send_type.MT_MODIFY_FRIEND_REMARK, data)
|
||||||
|
|
||||||
|
def get_room_name(self, room_wxid: str) -> str:
|
||||||
|
"""
|
||||||
|
获取群名
|
||||||
|
"""
|
||||||
|
sql = f"select nickname from contact where username='{room_wxid}'"
|
||||||
|
result = self.sql_query(sql, 1)["result"]
|
||||||
|
if result:
|
||||||
|
return result[0][0]
|
||||||
|
return ''
|
||||||
|
|
|
@ -8,3 +8,7 @@ class WeChatBindError(Exception):
|
||||||
|
|
||||||
class WeChatNotLoginError(Exception):
|
class WeChatNotLoginError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WeChatRuntimeError(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
|
||||||
from .xdg import get_log_dir
|
|
||||||
from .. import conf
|
from .. import conf
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,24 +17,6 @@ def get_logger(name: str) -> logging.Logger:
|
||||||
logger.setLevel(log_level)
|
logger.setLevel(log_level)
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
|
|
||||||
# create file handler and set level to debug
|
|
||||||
if conf.LOG_FILE_KEY in os.environ:
|
|
||||||
filepath = os.environ[conf.LOG_FILE_KEY]
|
|
||||||
else:
|
|
||||||
base_dir = get_log_dir()
|
|
||||||
if not os.path.exists(base_dir):
|
|
||||||
os.mkdir(base_dir)
|
|
||||||
|
|
||||||
time_now = datetime.now()
|
|
||||||
time_format = '%Y-%m-%d-%H-%M'
|
|
||||||
|
|
||||||
filepath = f'{base_dir}/log-{time_now.strftime(time_format)}.txt'
|
|
||||||
|
|
||||||
file_handler = logging.FileHandler(filepath, 'a', encoding='utf-8')
|
|
||||||
file_handler.setLevel(log_level)
|
|
||||||
file_handler.setFormatter(log_formatter)
|
|
||||||
logger.addHandler(file_handler)
|
|
||||||
|
|
||||||
# create console handler and set level to info
|
# create console handler and set level to info
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = logging.StreamHandler()
|
||||||
console_handler.setLevel(log_level)
|
console_handler.setLevel(log_level)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
from ntchat.wc import SUPPORT_VERSIONS
|
||||||
|
|
||||||
|
|
||||||
def get_exec_dir():
|
def get_exec_dir():
|
||||||
|
@ -23,12 +24,16 @@ def get_wc_dir():
|
||||||
|
|
||||||
|
|
||||||
def get_helper_file(version):
|
def get_helper_file(version):
|
||||||
return os.path.join(get_wc_dir(), f"helper_{version}.pyd")
|
return os.path.join(get_wc_dir(), f"helper_{version}.dat")
|
||||||
|
|
||||||
|
|
||||||
def get_support_download_url():
|
def has_helper_file():
|
||||||
return 'https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe'
|
for name in os.listdir(get_wc_dir()):
|
||||||
|
if name.startswith("helper_"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def is_support_version(version):
|
||||||
print(get_helper_file('3.6.0.18'))
|
return version in SUPPORT_VERSIONS
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
SUPPORT_VERSIONS = [
|
||||||
|
'3.6.0.18'
|
||||||
|
]
|
|
@ -3,6 +3,7 @@
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
|
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"
|
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"
|
||||||
pushd ..
|
pushd ..
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.6.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.6.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
|
@ -10,4 +11,5 @@ pushd ..
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
|
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0\python.exe -m pip install --upgrade pip setuptools wheel
|
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe -m pip install --upgrade pip setuptools wheel
|
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe -m pip install --upgrade pip setuptools wheel
|
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
|
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0\python.exe -m pip install --upgrade pip setuptools wheel
|
||||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||||
pushd ..
|
pushd ..
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.6.0\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.6.0\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
|
@ -10,4 +11,5 @@ pushd ..
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe setup.py bdist_wheel -d wheelhouse
|
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
|
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0\python.exe setup.py bdist_wheel -d wheelhouse
|
||||||
popd
|
popd
|
||||||
|
|
7
setup.py
7
setup.py
|
@ -194,7 +194,7 @@ extension.extra_compile_cpp_args = extra_compile_cpp_args[target_os]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='ntchat',
|
name='ntchat',
|
||||||
version='0.1.3',
|
version='0.1.17',
|
||||||
description='About Conversational RPA SDK for Chatbot Makers',
|
description='About Conversational RPA SDK for Chatbot Makers',
|
||||||
long_description="",
|
long_description="",
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
|
@ -209,9 +209,10 @@ setup(
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.10'
|
'Programming Language :: Python :: 3.10',
|
||||||
|
'Programming Language :: Python :: 3.11'
|
||||||
],
|
],
|
||||||
package_data={"": ["py.typed", "*.pyi", "helper*.pyd"]},
|
package_data={"": ["py.typed", "*.pyi", "helper*.dat"]},
|
||||||
include_package_data=False,
|
include_package_data=False,
|
||||||
packages=find_packages(include=['ntchat', 'ntchat.*']),
|
packages=find_packages(include=['ntchat', 'ntchat.*']),
|
||||||
keywords='wechat ntchat pywechat rebot',
|
keywords='wechat ntchat pywechat rebot',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user