Compare commits

...

31 Commits

Author SHA1 Message Date
evilbeast
da155c82e3 更新readme 2022-10-28 15:51:35 +08:00
evilbeast
92364c1e65 支持python3.11 2022-10-26 01:22:17 +08:00
evilbeast
90c824d0b0 去除sql_query例子 2022-10-09 12:46:55 +08:00
evilbeast
330f0724f3 优化异常输出 2022-10-09 12:37:03 +08:00
evilbeast
38506a0e7b 优化msg_register 2022-10-07 11:34:28 +08:00
evilbeast
f7fb727257 fastapi例子open接口返回二维码结果 2022-09-28 15:48:32 +08:00
evilbeast
ae450372b1 增强安全性 2022-09-28 10:56:35 +08:00
evilbeast
734eab020e 更新send_text_ui例子 2022-09-23 18:19:54 +08:00
evilbeast
89ea755606
Merge pull request #38 from IcePigZDB/add_transmit_example
add transmit example
2022-09-23 17:48:52 +08:00
daobing zhu
cdaacf186b address comment 2022-09-23 17:24:20 +08:00
daobing zhu
cd51f463bc remove unuse import 2022-09-23 08:43:35 +08:00
daobing zhu
99ac6bf007 add transmit example 2022-09-23 08:42:30 +08:00
evilbeast
476e8428cb fastapi添加修改备注接口 2022-09-20 09:29:09 +08:00
evilbeast
f7ba8fdbbf 修复消息回调函数有异常时程序会退出的问题 2022-09-19 17:05:42 +08:00
evilbeast
3f22840334 更新fastapi 2022-09-13 21:44:03 +08:00
evilbeast
15b408d390 使用绝对路径 2022-09-13 21:43:06 +08:00
evilbeast
5ff92ea0c7 修复python3.6运行bug 2022-09-13 21:39:19 +08:00
evilbeast
cf69a36a72 添加发送群@例子 2022-09-10 12:16:54 +08:00
evilbeast
d91c807905 更新 2022-09-09 15:27:01 +08:00
evilbeast
7a7e927442 更新models 2022-09-09 15:22:57 +08:00
evilbeast
9a0cc00493 修复在回调中调用同步接口返回None的问题 2022-09-09 13:42:09 +08:00
evilbeast
8db8998aaa 增加拍一拍 2022-09-08 12:30:39 +08:00
evilbeast
38d34ffb97 更新faq 2022-09-08 11:00:15 +08:00
evilbeast
eac6a6484a 更新faq 2022-09-08 10:58:45 +08:00
evilbeast
6e30f1f243 新增ImportError问题说明 2022-09-08 10:57:19 +08:00
evilbeast
e279b6e6fc 修复fastapi_example的get_contacts错误 2022-09-07 17:58:15 +08:00
evilbeast
b09ce23997 更新fastapi_example打包命令 2022-09-07 12:51:27 +08:00
evilbeast
da213b01af 更新README 2022-09-07 09:54:37 +08:00
evilbeast
80bc605bfa 添加数据库查询例子 2022-09-06 19:46:57 +08:00
evilbeast
008e6867ef 添加消息轰炸机示例 2022-09-06 13:17:07 +08:00
evilbeast
91d1257220 search_contacts方法添加模糊搜索参数 2022-09-06 11:31:16 +08:00
25 changed files with 443 additions and 54 deletions

3
.gitignore vendored
View File

@ -27,4 +27,5 @@ ntchat/wc/*.pyd
ntchat/wc/*.dat
wheelhouse/
setup_conf.py
upload.bat
upload.bat
download/

View File

@ -1,6 +1,6 @@
<h1 align="center">NtChat</h1>
<p align="center">
<a href="https://github.com/smallevilbeast/ntchat/releases"><img src="https://img.shields.io/badge/release-0.1.8-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>
</p>
@ -14,13 +14,12 @@
- 支持好友和群管理
## 支持的微信版本下载
- [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&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI)
- 查看 [NtChatHttp接口示例](fastapi_example)
- 查看 [PyXCGUI项目](https://github.com/smallevilbeast/pyxcgui)
## 安装
@ -146,7 +145,7 @@ except KeyboardInterrupt:
# -*- coding: utf8 -*-
import xcgui
import ntchat
from xcgui import XApp, XWindow
from xcgui import XApp, XWindow, RunUiThread
class NtChatWindow(XWindow):
@ -171,6 +170,8 @@ class NtChatWindow(XWindow):
def on_btn_open_clicked(self, sender, _):
self.wechat_instance = ntchat.WeChat()
self.wechat_instance.open(smart=True)
# 监听所有通知消息
self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message)
def on_btn_send_clicked(self, sender, _):
@ -182,6 +183,7 @@ class NtChatWindow(XWindow):
else:
self.wechat_instance.send_text(self.edit_wxid.getText(), self.edit_content.getText())
@RunUiThread()
def on_recv_message(self, wechat, message):
text = self.edit_log.getText()
text += "\n"
@ -199,7 +201,3 @@ if __name__ == '__main__':
app.exit()
```
帮助&支持
-------------------------
点击链接加入群聊 [PyXCGUI&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI)

View File

@ -1,4 +1,4 @@
## WeChatVersionNotMatchError异常
## 1. WeChatVersionNotMatchError异常
如果出现`ntchat.exception.WeChatVersionNotMatchError`异常, 请确认是否安装github上指定的微信版本如果确认已经安装还是报错可以在代码中添加以下代码跳过微信版本检测
```python
import ntchat
@ -23,8 +23,13 @@ Windows Registry Editor Version 5.00
"InstallPath"="C:\Program Files (x86)\Tencent\WeChat"
```
## 2. `ImportError: cannot import name 'wcprobe' from 'ntchat.wc'`
## 如何多开
出现在这个错误的原因是因为你在github下载的源码目录中运行程序因为wcprobe是根据python版本自动编译生成的所以源码目录中没有这个文件.
你需要将要`运行的例子文件移动到非源码目录下`,再去运行或打包
## 3. 如何多开
新建多个ntchat.WeChat实例然后调用open方法
```python
@ -37,7 +42,7 @@ for i in range(3):
```
更完善的多实例管理查看[fastapi_example例子](./fastapi_example)
## 如何监听输出所有的消息
## 4. 如何监听输出所有的消息
```python
# 注册监听所有消息回调
@wechat.msg_register(ntchat.MT_ALL)
@ -47,7 +52,7 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
```
完全例子查看[examples/msg_register_all.py](../examples/msg_register_all.py)
## 如何关闭NtChat的日志
## 5. 如何关闭NtChat的日志
`os.environ['NTCHAT_LOG'] = "ERROR"` 要在`import ntchat`前执行
```python
@ -60,7 +65,7 @@ os.environ['NTCHAT_LOG'] = "ERROR"
import ntchat
```
## 如何正常的关闭Cmd窗口
## 6. 如何正常的关闭Cmd窗口
先使用`pip install pywin32` 安装pywin32模块, 然后在代码中添加以下代码, 完整例子查看[examples/cmd_close_event.py](../examples/cmd_close_event.py)
```python
@ -78,7 +83,7 @@ win32api.SetConsoleCtrlHandler(on_exit, True)
```
## pyinstaller打包exe
## 7. pyinstaller打包exe
使用pyinstaller打包NtChat项目需要添加`--collect-data=ntchat`选项
打包成单个exe程序
@ -90,3 +95,8 @@ 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
```

View File

@ -22,7 +22,11 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message):
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等有主循环消息的框架可以去除下面代码

43
examples/bomber.py Normal file
View 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_()

View 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()

View File

@ -1,6 +1,6 @@
import xcgui
import ntchat
from xcgui import XApp, XWindow
from xcgui import XApp, XWindow, RunUiThread
class NtChatWindow(XWindow):
@ -38,6 +38,7 @@ class NtChatWindow(XWindow):
else:
self.wechat_instance.send_text(self.edit_wxid.getText(), self.edit_content.getText())
@RunUiThread()
def on_recv_message(self, wechat, message):
text = self.edit_log.getText()
text += "\n"

View 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
View 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()

View File

@ -1,8 +1,15 @@
import os.path
import time
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):
@ -11,7 +18,7 @@ def get_local_path(model: SendMediaReqModel):
if not model.url:
return None
data = requests.get(model.url).content
temp_file = os.path.join(get_download_dir(), str(time.time_ns()))
temp_file = new_download_file()
with open(temp_file, 'wb') as fp:
fp.write(data)
return temp_file

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import uvicorn
import threading
from functools import wraps
from fastapi import FastAPI
from mgr import ClientManager
from typing import List
from down import get_local_path
from exception import MediaNotExistsError, ClientNotExists
import models
@ -57,8 +57,14 @@ async def client_create():
response_model=models.ResponseModel)
@catch_exception()
async def client_open(model: models.ClientOpenReqModel):
ret = client_mgr.get_client(model.guid).open(model.smart)
return response_json(1 if ret else 0)
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"],
@ -82,7 +88,6 @@ async def user_get_profile(model: models.ClientReqModel):
@catch_exception()
async def get_contacts(model: models.ClientReqModel):
data = client_mgr.get_client(model.guid).get_contacts()
print(data)
return response_json(1, data)
@ -94,6 +99,13 @@ async def get_contact_detail(model: models.ContactDetailReqModel):
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()
@ -102,6 +114,17 @@ async def get_rooms(model: models.ClientReqModel):
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()
@ -244,5 +267,19 @@ async def send_gif(model: models.SendMediaReqModel):
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)
uvicorn.run(app=app, host='0.0.0.0', port=8000)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import ntchat
import threading
import requests
from typing import Dict, Union
from ntchat.utils.singleton import Singleton
@ -9,6 +10,8 @@ from exception import ClientNotExists
class ClientWeChat(ntchat.WeChat):
guid: str = ""
qrcode_event: threading.Event = None
qrcode: str = ""
class ClientManager(metaclass=Singleton):
@ -35,7 +38,7 @@ class ClientManager(metaclass=Singleton):
wechat.on(ntchat.MT_RECV_WECHAT_QUIT_MSG, self.__on_quit_callback)
return guid
def get_client(self, guid: str) -> Union[None, ntchat.WeChat]:
def get_client(self, guid: str) -> ClientWeChat:
client = self.__client_map.get(guid, None)
if client is None:
raise ClientNotExists(guid)
@ -45,7 +48,14 @@ class ClientManager(metaclass=Singleton):
if guid in self.__client_map:
del self.__client_map[guid]
def __on_callback(self, wechat, message):
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

View File

@ -87,6 +87,10 @@ class GetRoomMembersReqModel(ClientReqModel):
room_wxid: str
class GetRoomNameReqModel(ClientReqModel):
room_wxid: str
class CreateRoomReqModel(ClientReqModel):
member_list: List[str]
@ -135,3 +139,19 @@ class SendLinkCardReqModel(SendMsgReqModel):
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

View File

@ -9,6 +9,7 @@ def get_exec_dir():
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

View File

@ -16,4 +16,9 @@ def set_wechat_exe_path(wechat_exe_path=None, wechat_version=None):
conf.DEFAULT_WECHAT_VERSION = wechat_version
exit_ = wcprobe.exit
def get_install_wechat_version():
return wcprobe.get_install_wechat_version()
def exit_():
wcprobe.exit()

View File

@ -1,4 +1,4 @@
VERSION = '0.1.8'
VERSION = '0.1.17'
LOG_LEVEL = "DEBUG"
LOG_KEY = 'NTCHAT_LOG'

View File

@ -46,6 +46,9 @@ MT_SEND_GIF_MSG = 11043
# 发送xml消息
MT_SEND_XML_MSG = 11113
# 修改好友备注
MT_MODIFY_FRIEND_REMARK = 11063
# 接受新好友请求
MT_ACCEPT_FRIEND_MSG = 11065
@ -75,3 +78,6 @@ MT_ADD_FRIEND_MSG = 11062
# 数据库查询
MT_SQL_QUERY_MSG = 11027
# 拍一拍
MT_SEND_PAT_MSG = 11250

View File

@ -1,8 +1,8 @@
import json
import os.path
from ntchat.wc import wcprobe
from ntchat.utils.xdg import get_helper_file
from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError
from ntchat.wc import wcprobe, SUPPORT_VERSIONS
from ntchat.utils.xdg import get_helper_file, is_support_version, has_helper_file
from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError, WeChatRuntimeError
from ntchat.utils.singleton import Singleton
from ntchat.const import notify_type
from ntchat.utils.logger import get_logger
@ -31,9 +31,16 @@ class WeChatMgr(metaclass=Singleton):
else:
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)
if not os.path.exists(helper_file):
raise WeChatVersionNotMatchError()
raise WeChatRuntimeError("missing core files")
log.info("initialize wechat, version: %s", version)

View File

@ -41,6 +41,17 @@ class ReqData:
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:
client_id: int = 0
pid: int = 0
@ -51,34 +62,32 @@ class WeChat:
WeChatMgr().append_instance(self)
self.__wait_login_event = Event()
self.__req_data_cache = {}
self.__msg_event_emitter = pyee.EventEmitter()
self.event_emitter = pyee.EventEmitter()
self.__login_info = {}
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)):
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):
wraps(f)
for event in msg_type:
self.on(event, f)
self.on(msg_type, f)
return f
return wrapper
def on_close(self):
self.login_status = False
self.status = False
self.__msg_event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self)
self.event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self)
message = {
"type": notify_type.MT_RECV_WECHAT_QUIT_MSG,
"data": {}
}
self.__msg_event_emitter.emit(str(notify_type.MT_ALL), self, message)
self.event_emitter.emit(str(notify_type.MT_ALL), self, message)
def bind_client_id(self, client_id):
self.status = True
@ -102,14 +111,17 @@ class WeChat:
req_data.on_response(message)
del self.__req_data_cache[extend]
else:
self.__msg_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(msg_type), self, message)
self.event_emitter.emit(str(notify_type.MT_ALL), self, message)
def wait_login(self, timeout=None):
log.info("wait login...")
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)
log.info("open wechat pid: %d", self.pid)
return self.pid != 0
@ -200,7 +212,8 @@ class WeChat:
wxid: Union[None, str] = None,
account: Union[None, str] = None,
nickname: Union[None, str] = None,
remark: Union[None, str] = None):
remark: Union[None, str] = None,
fuzzy_search: bool = True):
"""
根据wxid微信号昵称和备注模糊搜索联系人
"""
@ -217,13 +230,13 @@ class WeChat:
return []
cond_pairs = []
tag = '%' if fuzzy_search else ''
for k, v in conds.items():
cond_pairs.append(f"{k} like '%{v}%'")
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)
print(message)
if not message:
return []
@ -359,6 +372,16 @@ class WeChat:
}
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):
"""
同意加好友请求
@ -446,3 +469,23 @@ class WeChat:
"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 ''

View File

@ -8,3 +8,7 @@ class WeChatBindError(Exception):
class WeChatNotLoginError(Exception):
pass
class WeChatRuntimeError(Exception):
pass

View File

@ -1,6 +1,7 @@
import os
import sys
import os.path
from ntchat.wc import SUPPORT_VERSIONS
def get_exec_dir():
@ -26,9 +27,13 @@ def get_helper_file(version):
return os.path.join(get_wc_dir(), f"helper_{version}.dat")
def get_support_download_url():
return 'https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe'
def has_helper_file():
for name in os.listdir(get_wc_dir()):
if name.startswith("helper_"):
return True
return False
if __name__ == '__main__':
print(get_helper_file('3.6.0.18'))
def is_support_version(version):
return version in SUPPORT_VERSIONS

View File

@ -0,0 +1,3 @@
SUPPORT_VERSIONS = [
'3.6.0.18'
]

View File

@ -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.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.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"
pushd ..
%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.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.11.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
popd

View File

@ -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.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.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"
pushd ..
%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.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.11.0\python.exe setup.py bdist_wheel -d wheelhouse
popd

View File

@ -194,7 +194,7 @@ extension.extra_compile_cpp_args = extra_compile_cpp_args[target_os]
setup(
name='ntchat',
version='0.1.8',
version='0.1.17',
description='About Conversational RPA SDK for Chatbot Makers',
long_description="",
long_description_content_type='text/markdown',
@ -209,7 +209,8 @@ setup(
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'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*.dat"]},
include_package_data=False,