diff --git a/.gitignore b/.gitignore
index 8d9bcf1..05bea95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,5 +24,8 @@ main.spec
build/
config.ini
ntchat/wc/*.pyd
+ntchat/wc/*.dat
wheelhouse/
-setup_conf.py
\ No newline at end of file
+setup_conf.py
+upload.bat
+download/
\ No newline at end of file
diff --git a/README.md b/README.md
index a38a16f..3d7f449 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
NtChat
-
+
@@ -14,7 +14,14 @@
- 支持好友和群管理
## 支持的微信版本下载
-- [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)
+- 查看 [PyXCGUI项目](https://github.com/smallevilbeast/pyxcgui)
## 安装
@@ -38,7 +45,7 @@ import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 等待登录
wechat.wait_login()
@@ -63,7 +70,7 @@ import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 等待登录
wechat.wait_login()
@@ -97,7 +104,7 @@ import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 注册消息回调
@@ -120,6 +127,14 @@ except KeyboardInterrupt:
sys.exit()
```
+## 使用fastapi框架实现的web api接口
+
+通过fastapi的swagger在线文档可以很方便的管理NtChat接口
+
+[查看fastapi_example例子](./fastapi_example)
+
+
+
## 使用pyxcgui界面库实现的简单例子
@@ -131,7 +146,7 @@ except KeyboardInterrupt:
# -*- coding: utf8 -*-
import xcgui
import ntchat
-from xcgui import XApp, XWindow
+from xcgui import XApp, XWindow, RunUiThread
class NtChatWindow(XWindow):
@@ -155,7 +170,9 @@ class NtChatWindow(XWindow):
def on_btn_open_clicked(self, sender, _):
self.wechat_instance = ntchat.WeChat()
- self.wechat_instance.open()
+ self.wechat_instance.open(smart=True)
+
+ # 监听所有通知消息
self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message)
def on_btn_send_clicked(self, sender, _):
@@ -167,6 +184,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"
diff --git a/docs/FAQ.md b/docs/FAQ.md
new file mode 100644
index 0000000..bde7be8
--- /dev/null
+++ b/docs/FAQ.md
@@ -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
+```
\ No newline at end of file
diff --git a/examples/auto_accept_friend_request.py b/examples/auto_accept_friend_request.py
index 0d29212..676883f 100644
--- a/examples/auto_accept_friend_request.py
+++ b/examples/auto_accept_friend_request.py
@@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
import sys
+import time
import ntchat
import xml.dom.minidom
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 注册消息回调
@@ -21,12 +22,18 @@ 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等有主循环消息的框架,可以去除下面代码
try:
while True:
- pass
+ time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()
+
diff --git a/examples/bomber.py b/examples/bomber.py
new file mode 100644
index 0000000..d1f8ee9
--- /dev/null
+++ b/examples/bomber.py
@@ -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_()
+
+
+
diff --git a/examples/close_log.py b/examples/close_log.py
new file mode 100644
index 0000000..e809e97
--- /dev/null
+++ b/examples/close_log.py
@@ -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()
diff --git a/examples/cmd_close_event.py b/examples/cmd_close_event.py
new file mode 100644
index 0000000..14d8d28
--- /dev/null
+++ b/examples/cmd_close_event.py
@@ -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()
diff --git a/examples/echo_bot_image.py b/examples/echo_bot_image.py
new file mode 100644
index 0000000..5cc6961
--- /dev/null
+++ b/examples/echo_bot_image.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+import sys
+import time
+import ntchat
+
+wechat = ntchat.WeChat()
+
+# 打开pc微信, smart: 是否管理已经登录的微信
+wechat.open(smart=True)
+
+
+# 注册消息回调
+@wechat.msg_register(ntchat.MT_RECV_PICTURE_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:
+ time.sleep(3)
+ wechat_instance.send_image(to_wxid=from_wxid, file_path=data["image"])
+
+
+# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
+try:
+ while True:
+ time.sleep(0.5)
+except KeyboardInterrupt:
+ ntchat.exit_()
+ sys.exit()
diff --git a/examples/echo_bot_msg_register.py b/examples/echo_bot_msg_register.py
index 685a703..84bf82b 100644
--- a/examples/echo_bot_msg_register.py
+++ b/examples/echo_bot_msg_register.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
import sys
+import time
import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 注册消息回调
@@ -14,15 +15,17 @@ 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:
+ # 判断消息不是自己发的并且不是群消息时,回复对方
+ 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:
- pass
+ time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()
diff --git a/examples/echo_bot_on.py b/examples/echo_bot_on.py
index 980e43d..32ccf54 100644
--- a/examples/echo_bot_on.py
+++ b/examples/echo_bot_on.py
@@ -1,29 +1,32 @@
# -*- coding: utf-8 -*-
import sys
+import time
import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
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:
+ # 判断消息不是自己发的并且不是群消息时,回复对方
+ if from_wxid != self_wxid and not room_wxid:
wechat_instance.send_text(to_wxid=from_wxid, content=f"你发送的消息是: {data['msg']}")
# 监听接收文本消息
wechat.on(ntchat.MT_RECV_TEXT_MSG, on_recv_text_msg)
+# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
try:
while True:
- pass
+ time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()
diff --git a/examples/get_contacts.py b/examples/get_contacts.py
index 9c17c69..7a6e1c1 100644
--- a/examples/get_contacts.py
+++ b/examples/get_contacts.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
import sys
+import time
import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 等待登录
wechat.wait_login()
@@ -17,9 +18,11 @@
print(contacts)
+# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
try:
while True:
- pass
+ time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()
+
diff --git a/examples/get_publics.py b/examples/get_publics.py
new file mode 100644
index 0000000..1a99452
--- /dev/null
+++ b/examples/get_publics.py
@@ -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()
diff --git a/examples/get_rooms.py b/examples/get_rooms.py
index 986bc2c..f187f7c 100644
--- a/examples/get_rooms.py
+++ b/examples/get_rooms.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
import sys
+import time
import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 等待登录
wechat.wait_login()
@@ -17,9 +18,11 @@
print(rooms)
+# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
try:
while True:
- pass
+ time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()
+
diff --git a/examples/msg_register_all.py b/examples/msg_register_all.py
new file mode 100644
index 0000000..d357acb
--- /dev/null
+++ b/examples/msg_register_all.py
@@ -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()
diff --git a/examples/multi_open.py b/examples/multi_open.py
index a9ed2e7..e79d41a 100644
--- a/examples/multi_open.py
+++ b/examples/multi_open.py
@@ -5,5 +5,5 @@
# 多开3个微信
for i in range(3):
wechat = ntchat.WeChat()
- wechat.open()
+ wechat.open(smart=False)
diff --git a/examples/quit_event.py b/examples/quit_event.py
new file mode 100644
index 0000000..d607b76
--- /dev/null
+++ b/examples/quit_event.py
@@ -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()
diff --git a/examples/resources/send_text_ui.jpg b/examples/resources/send_text_ui.jpg
deleted file mode 100644
index 8ee3ac2..0000000
Binary files a/examples/resources/send_text_ui.jpg and /dev/null differ
diff --git a/examples/schedule_send_text.py b/examples/schedule_send_text.py
new file mode 100644
index 0000000..08efe94
--- /dev/null
+++ b/examples/schedule_send_text.py
@@ -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()
+
+
+
+
diff --git a/examples/search_contacts.py b/examples/search_contacts.py
new file mode 100644
index 0000000..2987536
--- /dev/null
+++ b/examples/search_contacts.py
@@ -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()
diff --git a/examples/send_room_at_msg.py b/examples/send_room_at_msg.py
new file mode 100644
index 0000000..cc1478b
--- /dev/null
+++ b/examples/send_room_at_msg.py
@@ -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()
+
+
+
+
diff --git a/examples/send_text.py b/examples/send_text.py
index b6932e3..536aa3c 100644
--- a/examples/send_text.py
+++ b/examples/send_text.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
import sys
+import time
import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
-wechat.open(smart=False)
+wechat.open(smart=True)
# 等待登录
wechat.wait_login()
@@ -13,12 +14,14 @@
# 向文件助手发送一条消息
wechat.send_text(to_wxid="filehelper", content="hello, filehelper")
+# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码
try:
while True:
- pass
+ time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()
+
diff --git a/examples/send_text_ui.py b/examples/send_text_ui.py
index 64543c8..59f1aad 100644
--- a/examples/send_text_ui.py
+++ b/examples/send_text_ui.py
@@ -1,6 +1,6 @@
import xcgui
import ntchat
-from xcgui import XApp, XWindow
+from xcgui import XApp, XWindow, RunUiThread
class NtChatWindow(XWindow):
@@ -24,7 +24,9 @@ def __init__(self):
def on_btn_open_clicked(self, sender, _):
self.wechat_instance = ntchat.WeChat()
- self.wechat_instance.open()
+ self.wechat_instance.open(smart=True)
+
+ # 监听所有通知消息
self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message)
def on_btn_send_clicked(self, sender, _):
@@ -36,6 +38,7 @@ def on_btn_send_clicked(self, sender, _):
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"
diff --git a/examples/show_login_qrcode.py b/examples/show_login_qrcode.py
new file mode 100644
index 0000000..49018ba
--- /dev/null
+++ b/examples/show_login_qrcode.py
@@ -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)
diff --git a/examples/test.py b/examples/test.py
deleted file mode 100644
index b15c3a0..0000000
--- a/examples/test.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import xml.dom.minidom
-
-content = ""
-dom = xml.dom.minidom.parseString(content)
-encryptusername = dom.documentElement.getAttribute("encryptusername")
-ticket = dom.documentElement.getAttribute("ticket")
-scene = dom.documentElement.getAttribute("scene")
-print(dom.documentElement.getAttribute("encryptusername"))
\ No newline at end of file
diff --git a/examples/transmit.py b/examples/transmit.py
new file mode 100644
index 0000000..9062f84
--- /dev/null
+++ b/examples/transmit.py
@@ -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()
\ No newline at end of file
diff --git a/fastapi_example/README.md b/fastapi_example/README.md
new file mode 100644
index 0000000..ad6c5a1
--- /dev/null
+++ b/fastapi_example/README.md
@@ -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 是打开并管理上微信实例
\ No newline at end of file
diff --git a/fastapi_example/__init__.py b/fastapi_example/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/fastapi_example/down.py b/fastapi_example/down.py
new file mode 100644
index 0000000..b00582d
--- /dev/null
+++ b/fastapi_example/down.py
@@ -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
diff --git a/fastapi_example/exception.py b/fastapi_example/exception.py
new file mode 100644
index 0000000..d80b757
--- /dev/null
+++ b/fastapi_example/exception.py
@@ -0,0 +1,9 @@
+class ClientNotExists(Exception):
+ guid = ""
+
+ def __init__(self, guid):
+ self.guid = guid
+
+
+class MediaNotExistsError(Exception):
+ pass
diff --git a/fastapi_example/main.py b/fastapi_example/main.py
new file mode 100644
index 0000000..5bcff80
--- /dev/null
+++ b/fastapi_example/main.py
@@ -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)
diff --git a/fastapi_example/mgr.py b/fastapi_example/mgr.py
new file mode 100644
index 0000000..61f8e04
--- /dev/null
+++ b/fastapi_example/mgr.py
@@ -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": {}})
diff --git a/fastapi_example/models.py b/fastapi_example/models.py
new file mode 100644
index 0000000..1028c51
--- /dev/null
+++ b/fastapi_example/models.py
@@ -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
+
+
diff --git a/fastapi_example/requirements.txt b/fastapi_example/requirements.txt
new file mode 100644
index 0000000..15cb0c7
--- /dev/null
+++ b/fastapi_example/requirements.txt
@@ -0,0 +1,4 @@
+ntchat
+fastapi
+requests
+uvicorn
\ No newline at end of file
diff --git a/fastapi_example/utils.py b/fastapi_example/utils.py
new file mode 100644
index 0000000..bac1664
--- /dev/null
+++ b/fastapi_example/utils.py
@@ -0,0 +1,6 @@
+import uuid
+import time
+
+
+def generate_guid(prefix=''):
+ return str(uuid.uuid3(uuid.NAMESPACE_URL, prefix + str(time.time())))
diff --git a/fastapi_example/xdg.py b/fastapi_example/xdg.py
new file mode 100644
index 0000000..3243b8d
--- /dev/null
+++ b/fastapi_example/xdg.py
@@ -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
diff --git a/ntchat/__init__.py b/ntchat/__init__.py
index 437b2cb..d24e420 100644
--- a/ntchat/__init__.py
+++ b/ntchat/__init__.py
@@ -1,9 +1,24 @@
from .conf import VERSION
from .core.wechat import WeChat
from .wc import wcprobe
-from .const.wx_type import *
+from .const.notify_type import *
from .exception import *
+from . import conf
__version__ = VERSION
-exit_ = wcprobe.exit
+
+def set_wechat_exe_path(wechat_exe_path=None, wechat_version=None):
+ """
+ 自定义微信路径
+ """
+ conf.DEFAULT_WECHAT_EXE_PATH = wechat_exe_path
+ conf.DEFAULT_WECHAT_VERSION = wechat_version
+
+
+def get_install_wechat_version():
+ return wcprobe.get_install_wechat_version()
+
+
+def exit_():
+ wcprobe.exit()
diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py
index 8d76f25..1e3ba15 100644
--- a/ntchat/conf/__init__.py
+++ b/ntchat/conf/__init__.py
@@ -1 +1,9 @@
-VERSION = '0.1.1'
+VERSION = '0.1.16'
+
+LOG_LEVEL = "DEBUG"
+LOG_KEY = 'NTCHAT_LOG'
+LOG_FILE_KEY = 'NTCHAT_LOG_FILE'
+
+DEFAULT_WECHAT_EXE_PATH = None
+DEFAULT_WECHAT_VERSION = None
+
diff --git a/ntchat/const/notify_type.py b/ntchat/const/notify_type.py
new file mode 100644
index 0000000..e1d017f
--- /dev/null
+++ b/ntchat/const/notify_type.py
@@ -0,0 +1,86 @@
+# 用于接收所有的通知消息
+MT_ALL = 11000
+
+# 微信进程退出通知
+MT_RECV_WECHAT_QUIT_MSG = 11001
+
+# 第个通知消息,此时已经托管上微信
+MT_READY_MSG = 11024
+
+# 登录二维码通知
+MT_RECV_LOGIN_QRCODE_MSG = 11087
+
+# 用户登录成功的通知
+MT_USER_LOGIN_MSG = 11025
+
+# 用户注销或退出微信的通知
+MT_USER_LOGOUT_MSG = 11026
+
+# 文本消息通知
+MT_RECV_TEXT_MSG = 11046
+
+# 图片消息通知
+MT_RECV_IMAGE_MSG = 11047
+MT_RECV_PICTURE_MSG = 11047
+
+# 语音消息通知
+MT_RECV_VOICE_MSG = 11048
+
+# 新好友请求通知
+MT_RECV_FRIEND_MSG = 11049
+
+# 好友分享名片通知
+MT_RECV_CARD_MSG = 11050
+
+# 视频消息通知
+MT_RECV_VIDEO_MSG = 11051
+
+# 表情消息通知
+MT_RECV_EMOJI_MSG = 11052
+
+# 位置消息通知
+MT_RECV_LOCATION_MSG = 11053
+
+# 链接卡片消息通知
+MT_RECV_LINK_MSG = 11054
+
+# 文件消息通知
+MT_RECV_FILE_MSG = 11055
+
+# 小程序消息通知
+MT_RECV_MINIAPP_MSG = 11056
+
+# 二维码支付通知
+MT_RECV_WCPAY_MSG = 11057
+
+# 系统消息通知
+MT_RECV_SYSTEM_MSG = 11058
+
+# 撤回消息通知
+MT_RECV_REVOKE_MSG = 11059
+
+# 未知消息通知
+MT_RECV_OTHER_MSG = 11060
+
+# 未知应用消息通知
+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
+
+
diff --git a/ntchat/const/send_type.py b/ntchat/const/send_type.py
new file mode 100644
index 0000000..5fc5793
--- /dev/null
+++ b/ntchat/const/send_type.py
@@ -0,0 +1,83 @@
+# 获取自己的帐号信息
+MT_GET_SELF_MSG = 11028
+
+# 获取所有的联系人
+MT_GET_CONTACTS_MSG = 11030
+
+# 获取所有的群
+MT_GET_ROOMS_MSG = 11031
+
+# 获取公众号列表
+MT_GET_PUBLICS_MSG = 11033
+
+# 获取指定的群成员
+MT_GET_ROOM_MEMBERS_MSG = 11032
+
+# 获取指定联系人的详细信息
+MT_GET_CONTACT_DETAIL_MSG = 11029
+
+# 获取指定群的详细信息
+MT_GET_ROOM_DETAIL_MSG = 11125
+
+# 发送文本消息
+MT_SEND_TEXT_MSG = 11036
+
+# 发送群@消息
+MT_SEND_ROOM_AT_MSG = 11037
+
+# 发送名片消息
+MT_SEND_CARD_MSG = 11038
+
+# 发送链接卡片消息
+MT_SEND_LINK_MSG = 11039
+
+# 发送图片消息
+MT_SEND_IMAGE_MSG = 11040
+
+# 发送文件消息
+MT_SEND_FILE_MSG = 11041
+
+# 发送视频消息
+MT_SEND_VIDEO_MSG = 11042
+
+# 发送gif消息
+MT_SEND_GIF_MSG = 11043
+
+# 发送xml消息
+MT_SEND_XML_MSG = 11113
+
+# 修改好友备注
+MT_MODIFY_FRIEND_REMARK = 11063
+
+# 接受新好友请求
+MT_ACCEPT_FRIEND_MSG = 11065
+
+# 创建群
+MT_CREATE_ROOM_MSG = 11068
+
+# 添加好友进群
+MT_ADD_TO_ROOM_MSG = 11069
+
+# 邀请好友进群
+MT_INVITE_TO_ROOM_MSG = 11070
+
+# 移除群成员
+MT_DEL_ROOM_MEMBER_MSG = 11071
+
+# 修改群名
+MT_MOD_ROOM_NAME_MSG = 11072
+
+# 修改群公告
+MT_MOD_ROOM_NOTICE_MSG = 11073
+
+# 退出群
+MT_QUIT_DEL_ROOM_MSG = 11077
+
+# 添加群成员为好友
+MT_ADD_FRIEND_MSG = 11062
+
+# 数据库查询
+MT_SQL_QUERY_MSG = 11027
+
+# 拍一拍
+MT_SEND_PAT_MSG = 11250
diff --git a/ntchat/const/wx_type.py b/ntchat/const/wx_type.py
deleted file mode 100644
index 538ddab..0000000
--- a/ntchat/const/wx_type.py
+++ /dev/null
@@ -1,50 +0,0 @@
-MT_ALL = 11000
-MT_READY_MSG = 11024
-MT_USER_LOGIN_MSG = 11025
-MT_USER_LOGOUT_MSG = 11026
-MT_GET_SELF_MSG = 11028
-MT_GET_CONTACTS_MSG = 11030
-MT_GET_ROOMS_MSG = 11031
-MT_GET_ROOM_MEMBERS_MSG = 11032
-MT_GET_CONTACT_DETAIL_MSG = 11034
-
-# 发送消息
-MT_SEND_TEXT_MSG = 11036
-MT_SEND_ROOM_AT_MSG = 11037
-MT_SEND_CARD_MSG = 11038
-MT_SEND_LINK_MSG = 11039
-MT_SEND_IMAGE_MSG = 11040
-MT_SEND_FILE_MSG = 11041
-MT_SEND_VIDEO_MSG = 11042
-MT_SEND_GIF_MSG = 11043
-
-# 接收消息类
-MT_RECV_TEXT_MSG = 11046
-MT_RECV_PICTURE_MSG = 11047
-MT_RECV_VOICE_MSG = 11048
-MT_RECV_FRIEND_MSG = 11049
-MT_RECV_CARD_MSG = 11050
-MT_RECV_VIDEO_MSG = 11051
-MT_RECV_EMOJI_MSG = 11052
-MT_RECV_LOCATION_MSG = 11053
-MT_RECV_LINK_MSG = 11054
-MT_RECV_FILE_MSG = 11055
-MT_RECV_MINIAPP_MSG = 11056
-MT_RECV_WCPAY_MSG = 11057
-MT_RECV_SYSTEM_MSG = 11058
-MT_RECV_REVOKE_MSG = 11059
-MT_RECV_OTHER_MSG = 11060
-MT_RECV_OTHER_APP_MSG = 11061
-
-# 好友操作
-MT_ACCEPT_FRIEND_MSG = 11065
-
-# 群操作类
-MT_CREATE_ROOM_MSG = 11068
-MT_INVITE_TO_ROOM_MSG = 11069
-MT_INVITE_TO_ROOM_REQ_MSG = 11070
-MT_DEL_ROOM_MEMBER_MSG = 11071
-MT_MOD_ROOM_NAME_MSG = 11072
-MT_MOD_ROOM_NOTICE_MSG = 11073
-MT_QUIT_DEL_ROOM_MSG = 11077
-
diff --git a/ntchat/core/mgr.py b/ntchat/core/mgr.py
index cc4cab5..baacf1e 100644
--- a/ntchat/core/mgr.py
+++ b/ntchat/core/mgr.py
@@ -1,11 +1,12 @@
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 wx_type
+from ntchat.const import notify_type
from ntchat.utils.logger import get_logger
+from ntchat import conf
log = get_logger("WeChatManager")
@@ -14,8 +15,8 @@ class WeChatMgr(metaclass=Singleton):
__instance_list = []
__instance_map = {}
- def __init__(self, wechat_exe_path=None, wechat_version=None):
- self.set_wechat_exe_path(wechat_exe_path, wechat_version)
+ def __init__(self):
+ self.set_wechat_exe_path(conf.DEFAULT_WECHAT_EXE_PATH, conf.DEFAULT_WECHAT_VERSION)
# init callbacks
wcprobe.init_callback(self.__on_accept, self.__on_recv, self.__on_close)
@@ -30,9 +31,16 @@ def set_wechat_exe_path(self, wechat_exe_path=None, wechat_version=None):
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)
@@ -48,8 +56,7 @@ def __bind_wechat(self, client_id, pid):
if client_id not in self.__instance_map:
for instance in self.__instance_list:
if instance.pid == pid:
- instance.client_id = client_id
- instance.status = True
+ instance.bind_client_id(client_id)
self.__instance_map[client_id] = instance
bind_instance = instance
break
@@ -62,7 +69,7 @@ def __on_accept(self, client_id):
def __on_recv(self, client_id, data):
message = json.loads(data)
- if message["type"] == wx_type.MT_READY_MSG:
+ if message["type"] == notify_type.MT_READY_MSG:
self.__bind_wechat(client_id, message["data"]["pid"])
else:
self.__instance_map[client_id].on_recv(message)
@@ -70,5 +77,4 @@ def __on_recv(self, client_id, data):
def __on_close(self, client_id):
log.debug("close client_id: %d", client_id)
if client_id in self.__instance_map:
- self.__instance_map[client_id].login_status = False
- self.__instance_map[client_id].status = False
+ self.__instance_map[client_id].on_close()
diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py
index 921d71a..49df2c2 100644
--- a/ntchat/core/wechat.py
+++ b/ntchat/core/wechat.py
@@ -1,7 +1,7 @@
import pyee
import json
from ntchat.core.mgr import WeChatMgr
-from ntchat.const import wx_type
+from ntchat.const import notify_type, send_type
from threading import Event
from ntchat.wc import wcprobe
from ntchat.utils import generate_guid
@@ -41,6 +41,17 @@ def get_response_data(self):
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,33 +62,47 @@ def __init__(self):
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.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):
log.debug("on recv message: %s", message)
msg_type = message["type"]
extend = message.get("extend", None)
- if msg_type == wx_type.MT_USER_LOGIN_MSG:
+ if msg_type == notify_type.MT_USER_LOGIN_MSG:
self.login_status = True
self.__wait_login_event.set()
self.__login_info = message.get("data", {})
log.info("login success, wxid: %s, nickname: %s", self.__login_info["wxid"], self.__login_info["nickname"])
- elif msg_type == wx_type.MT_USER_LOGOUT_MSG:
+ elif msg_type == notify_type.MT_USER_LOGOUT_MSG:
self.login_status = False
log.info("logout, pid: %d", self.pid)
@@ -86,14 +111,17 @@ def on_recv(self, message):
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(wx_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
@@ -137,6 +165,16 @@ def __new_extend(self):
def __repr__(self):
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):
"""
获取登录信息
@@ -147,25 +185,87 @@ def get_self_info(self):
"""
获取自己个人信息跟登录信息类似
"""
- return self.__send_sync(wx_type.MT_GET_SELF_MSG)
+ return self.__send_sync(send_type.MT_GET_SELF_MSG)
def get_contacts(self):
"""
获取联系人列表
"""
- return self.__send_sync(wx_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):
+ """
+ 获取联系人详细信息
+ """
data = {
"wxid": wxid
}
- return self.__send_sync(wx_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):
"""
获取群列表
"""
- return self.__send_sync(wx_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):
"""
@@ -174,7 +274,7 @@ def get_room_members(self, room_wxid: str):
data = {
"room_wxid": room_wxid
}
- return self.__send_sync(wx_type.MT_GET_ROOM_MEMBERS_MSG, data)
+ return self.__send_sync(send_type.MT_GET_ROOM_MEMBERS_MSG, data)
def send_text(self, to_wxid: str, content: str):
"""
@@ -184,7 +284,7 @@ def send_text(self, to_wxid: str, content: str):
"to_wxid": to_wxid,
"content": content
}
- return self.__send(wx_type.MT_SEND_TEXT_MSG, data)
+ return self.__send(send_type.MT_SEND_TEXT_MSG, data)
def send_room_at_msg(self, to_wxid: str, content: str, at_list: List[str]):
"""
@@ -195,7 +295,7 @@ def send_room_at_msg(self, to_wxid: str, content: str, at_list: List[str]):
'content': content,
'at_list': at_list
}
- return self.__send(wx_type.MT_SEND_ROOM_AT_MSG, data)
+ return self.__send(send_type.MT_SEND_ROOM_AT_MSG, data)
def send_card(self, to_wxid: str, card_wxid: str):
"""
@@ -205,7 +305,7 @@ def send_card(self, to_wxid: str, card_wxid: str):
'to_wxid': to_wxid,
'card_wxid': card_wxid
}
- return self.__send(wx_type.MT_SEND_CARD_MSG, data)
+ return self.__send(send_type.MT_SEND_CARD_MSG, data)
def send_link_card(self, to_wxid: str, title: str, desc: str, url: str, image_url: str):
"""
@@ -218,7 +318,7 @@ def send_link_card(self, to_wxid: str, title: str, desc: str, url: str, image_ur
'url': url,
'image_url': image_url
}
- return self.__send(wx_type.MT_SEND_LINK_MSG, data)
+ return self.__send(send_type.MT_SEND_LINK_MSG, data)
def send_image(self, to_wxid: str, file_path: str):
"""
@@ -228,7 +328,7 @@ def send_image(self, to_wxid: str, file_path: str):
'to_wxid': to_wxid,
'file': file_path
}
- return self.__send(wx_type.MT_SEND_IMAGE_MSG, data)
+ return self.__send(send_type.MT_SEND_IMAGE_MSG, data)
def send_file(self, to_wxid: str, file_path: str):
"""
@@ -238,7 +338,7 @@ def send_file(self, to_wxid: str, file_path: str):
'to_wxid': to_wxid,
'file': file_path
}
- return self.__send(wx_type.MT_SEND_FILE_MSG, data)
+ return self.__send(send_type.MT_SEND_FILE_MSG, data)
#
def send_video(self, to_wxid: str, file_path: str):
@@ -249,7 +349,7 @@ def send_video(self, to_wxid: str, file_path: str):
'to_wxid': to_wxid,
'file': file_path
}
- return self.__send(wx_type.MT_SEND_VIDEO_MSG, data)
+ return self.__send(send_type.MT_SEND_VIDEO_MSG, data)
def send_gif(self, to_wxid, file):
"""
@@ -259,7 +359,28 @@ def send_gif(self, to_wxid, file):
'to_wxid': to_wxid,
'file': file
}
- return self.__send(wx_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):
"""
@@ -270,13 +391,13 @@ def accept_friend_request(self, encryptusername: str, ticket: str, scene: int):
"ticket": ticket,
"scene": scene
}
- return self.__send_sync(wx_type.MT_ACCEPT_FRIEND_MSG, data)
+ return self.__send_sync(send_type.MT_ACCEPT_FRIEND_MSG, data)
def create_room(self, member_list: List[str]):
"""
创建群
"""
- return self.__send(wx_type.MT_CREATE_ROOM_MSG, member_list)
+ return self.__send(send_type.MT_CREATE_ROOM_MSG, member_list)
def add_room_member(self, room_wxid: str, member_list: List[str]):
"""
@@ -286,7 +407,7 @@ def add_room_member(self, room_wxid: str, member_list: List[str]):
"room_wxid": room_wxid,
"member_list": member_list
}
- return self.__send_sync(wx_type.MT_INVITE_TO_ROOM_MSG, data)
+ return self.__send_sync(send_type.MT_ADD_TO_ROOM_MSG, data)
def invite_room_member(self, room_wxid: str, member_list: List[str]):
"""
@@ -296,7 +417,7 @@ def invite_room_member(self, room_wxid: str, member_list: List[str]):
"room_wxid": room_wxid,
"member_list": member_list
}
- return self.__send_sync(wx_type.MT_INVITE_TO_ROOM_REQ_MSG, data)
+ return self.__send_sync(send_type.MT_INVITE_TO_ROOM_MSG, data)
def del_room_member(self, room_wxid: str, member_list: List[str]):
"""
@@ -306,7 +427,7 @@ def del_room_member(self, room_wxid: str, member_list: List[str]):
"room_wxid": room_wxid,
"member_list": member_list
}
- return self.__send_sync(wx_type.MT_DEL_ROOM_MEMBER_MSG, data)
+ return self.__send_sync(send_type.MT_DEL_ROOM_MEMBER_MSG, data)
def modify_room_name(self, room_wxid: str, name: str):
"""
@@ -316,7 +437,7 @@ def modify_room_name(self, room_wxid: str, name: str):
"room_wxid": room_wxid,
"name": name
}
- return self.__send_sync(wx_type.MT_MOD_ROOM_NAME_MSG, data)
+ return self.__send_sync(send_type.MT_MOD_ROOM_NAME_MSG, data)
def modify_room_notice(self, room_wxid: str, notice: str):
"""
@@ -326,6 +447,45 @@ def modify_room_notice(self, room_wxid: str, notice: str):
"room_wxid": room_wxid,
"notice": notice
}
- return self.__send_sync(wx_type.MT_MOD_ROOM_NOTICE_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):
+ """
+ 添加群成员为好友
+ """
+ data = {
+ "room_wxid": room_wxid,
+ "wxid": wxid,
+ "source_type": 14,
+ "remark": verify
+ }
+ 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 ''
diff --git a/ntchat/exception/__init__.py b/ntchat/exception/__init__.py
index 1f65fd6..c15f237 100644
--- a/ntchat/exception/__init__.py
+++ b/ntchat/exception/__init__.py
@@ -8,3 +8,7 @@ class WeChatBindError(Exception):
class WeChatNotLoginError(Exception):
pass
+
+
+class WeChatRuntimeError(Exception):
+ pass
diff --git a/ntchat/utils/logger.py b/ntchat/utils/logger.py
index 09d5477..35d8227 100644
--- a/ntchat/utils/logger.py
+++ b/ntchat/utils/logger.py
@@ -1,60 +1,25 @@
-import logging
import os
-import configparser
-from datetime import datetime
-from .xdg import get_log_dir, get_exec_dir
-
-NTCHAT_LOG_KEY = 'NTCHAT_LOG'
-NTCHAT_LOG_FILE_KEY = 'NTCHAT_LOG_FILE'
-
-
-config_file = os.path.join(get_exec_dir(), "config.ini")
-CONFIG_DEBUG_LEVEL = ''
-
-if os.path.exists(config_file):
- config = configparser.ConfigParser()
- config.read(config_file)
- CONFIG_DEBUG_LEVEL = config.get('Config', 'LogLevel', fallback=CONFIG_DEBUG_LEVEL)
+import logging
+from .. import conf
def get_logger(name: str) -> logging.Logger:
"""
configured Loggers
"""
- NTCHAT_LOG = os.environ.get(NTCHAT_LOG_KEY, 'DEBUG')
+ log_level = os.environ.get(conf.LOG_KEY, conf.LOG_LEVEL)
log_formatter = logging.Formatter(
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
- if CONFIG_DEBUG_LEVEL:
- NTCHAT_LOG = CONFIG_DEBUG_LEVEL
-
# create logger and set level to debug
logger = logging.getLogger(name)
logger.handlers = []
- logger.setLevel(NTCHAT_LOG)
+ logger.setLevel(log_level)
logger.propagate = False
- # create file handler and set level to debug
- if NTCHAT_LOG_FILE_KEY in os.environ:
- filepath = os.environ[NTCHAT_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(NTCHAT_LOG)
- file_handler.setFormatter(log_formatter)
- logger.addHandler(file_handler)
-
# create console handler and set level to info
console_handler = logging.StreamHandler()
- console_handler.setLevel(NTCHAT_LOG)
+ console_handler.setLevel(log_level)
console_handler.setFormatter(log_formatter)
logger.addHandler(console_handler)
diff --git a/ntchat/utils/xdg.py b/ntchat/utils/xdg.py
index 339972e..d0836a7 100644
--- a/ntchat/utils/xdg.py
+++ b/ntchat/utils/xdg.py
@@ -1,6 +1,7 @@
import os
import sys
import os.path
+from ntchat.wc import SUPPORT_VERSIONS
def get_exec_dir():
@@ -23,12 +24,16 @@ def get_wc_dir():
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():
- 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
+
diff --git a/ntchat/wc/__init__.py b/ntchat/wc/__init__.py
index e69de29..3f6d525 100644
--- a/ntchat/wc/__init__.py
+++ b/ntchat/wc/__init__.py
@@ -0,0 +1,3 @@
+SUPPORT_VERSIONS = [
+ '3.6.0.18'
+]
diff --git a/setup.py b/setup.py
index b5b7aa3..10a6b87 100644
--- a/setup.py
+++ b/setup.py
@@ -194,7 +194,7 @@ def add_prefix(l, prefix):
setup(
name='ntchat',
- version='0.1.1',
+ version='0.1.16',
description='About Conversational RPA SDK for Chatbot Makers',
long_description="",
long_description_content_type='text/markdown',
@@ -211,7 +211,7 @@ def add_prefix(l, prefix):
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10'
],
- package_data={"": ["py.typed", "*.pyi", "helper*.pyd"]},
+ package_data={"": ["py.typed", "*.pyi", "helper*.dat"]},
include_package_data=False,
packages=find_packages(include=['ntchat', 'ntchat.*']),
keywords='wechat ntchat pywechat rebot',