Compare commits

...

20 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
22 changed files with 356 additions and 76 deletions

3
.gitignore vendored
View File

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

View File

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

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

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)

View File

@ -1,34 +0,0 @@
# -*- coding: utf-8 -*-
import sys
import time
import ntchat
wechat = ntchat.WeChat()
# 打开pc微信, smart: 是否管理已经登录的微信
wechat.open(smart=True)
# 等待登录
wechat.wait_login()
# 获取群列表并输出
room_wxid = wechat.get_rooms()[0]["wxid"]
def get_room_name(wechat: ntchat.WeChat, room_wxid: str):
sql = f"select nickname from contact where username='{room_wxid}'"
result = wechat.sql_query(sql, 1)["result"]
if result:
return result[0][0]
return None
print("群名是: ", get_room_name(wechat, room_wxid))
# 以下是为了让程序不结束如果有用于PyQt等有主循环消息的框架可以去除下面代码
try:
while True:
time.sleep(0.5)
except KeyboardInterrupt:
ntchat.exit_()
sys.exit()

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

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import uvicorn import uvicorn
import threading
from functools import wraps from functools import wraps
from fastapi import FastAPI from fastapi import FastAPI
from mgr import ClientManager from mgr import ClientManager
from typing import List
from down import get_local_path from down import get_local_path
from exception import MediaNotExistsError, ClientNotExists from exception import MediaNotExistsError, ClientNotExists
import models import models
@ -57,8 +57,14 @@ async def client_create():
response_model=models.ResponseModel) response_model=models.ResponseModel)
@catch_exception() @catch_exception()
async def client_open(model: models.ClientOpenReqModel): async def client_open(model: models.ClientOpenReqModel):
ret = client_mgr.get_client(model.guid).open(model.smart) client = client_mgr.get_client(model.guid)
return response_json(1 if ret else 0) 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"], @app.post("/global/set_callback_url", summary="设置接收通知地址", tags=["Global"],
@ -93,6 +99,13 @@ async def get_contact_detail(model: models.ContactDetailReqModel):
return response_json(1, data) 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"], @app.post("/room/get_rooms", summary="获取群列表", tags=["Room"],
response_model=models.ResponseModel) response_model=models.ResponseModel)
@catch_exception() @catch_exception()
@ -101,6 +114,17 @@ async def get_rooms(model: models.ClientReqModel):
return response_json(1, data) 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"], @app.post("/room/get_room_members", summary="获取群成员列表", tags=["Room"],
response_model=models.ResponseModel) response_model=models.ResponseModel)
@catch_exception() @catch_exception()
@ -243,5 +267,19 @@ async def send_gif(model: models.SendMediaReqModel):
return response_json(1 if ret else 0) 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__': 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 -*- # -*- coding: utf-8 -*-
import ntchat import ntchat
import threading
import requests import requests
from typing import Dict, Union from typing import Dict, Union
from ntchat.utils.singleton import Singleton from ntchat.utils.singleton import Singleton
@ -9,6 +10,8 @@ from exception import ClientNotExists
class ClientWeChat(ntchat.WeChat): class ClientWeChat(ntchat.WeChat):
guid: str = "" guid: str = ""
qrcode_event: threading.Event = None
qrcode: str = ""
class ClientManager(metaclass=Singleton): class ClientManager(metaclass=Singleton):
@ -35,7 +38,7 @@ class ClientManager(metaclass=Singleton):
wechat.on(ntchat.MT_RECV_WECHAT_QUIT_MSG, self.__on_quit_callback) wechat.on(ntchat.MT_RECV_WECHAT_QUIT_MSG, self.__on_quit_callback)
return guid 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) client = self.__client_map.get(guid, None)
if client is None: if client is None:
raise ClientNotExists(guid) raise ClientNotExists(guid)
@ -45,7 +48,14 @@ class ClientManager(metaclass=Singleton):
if guid in self.__client_map: if guid in self.__client_map:
del self.__client_map[guid] 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: if not self.callback_url:
return return

View File

@ -87,6 +87,10 @@ class GetRoomMembersReqModel(ClientReqModel):
room_wxid: str room_wxid: str
class GetRoomNameReqModel(ClientReqModel):
room_wxid: str
class CreateRoomReqModel(ClientReqModel): class CreateRoomReqModel(ClientReqModel):
member_list: List[str] member_list: List[str]
@ -135,3 +139,19 @@ class SendLinkCardReqModel(SendMsgReqModel):
class SendMediaReqModel(SendMsgReqModel): class SendMediaReqModel(SendMsgReqModel):
file_path: Optional[str] = "" file_path: Optional[str] = ""
url: 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(): def get_download_dir():
user_dir = os.path.join(get_exec_dir(), 'download') user_dir = os.path.join(get_exec_dir(), 'download')
user_dir = os.path.abspath(user_dir)
if not os.path.isdir(user_dir): if not os.path.isdir(user_dir):
os.makedirs(user_dir) os.makedirs(user_dir)
return user_dir return user_dir

View File

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

View File

@ -46,6 +46,9 @@ MT_SEND_GIF_MSG = 11043
# 发送xml消息 # 发送xml消息
MT_SEND_XML_MSG = 11113 MT_SEND_XML_MSG = 11113
# 修改好友备注
MT_MODIFY_FRIEND_REMARK = 11063
# 接受新好友请求 # 接受新好友请求
MT_ACCEPT_FRIEND_MSG = 11065 MT_ACCEPT_FRIEND_MSG = 11065

View File

@ -1,8 +1,8 @@
import json import json
import os.path import os.path
from ntchat.wc import wcprobe from ntchat.wc import wcprobe, SUPPORT_VERSIONS
from ntchat.utils.xdg import get_helper_file from ntchat.utils.xdg import get_helper_file, is_support_version, has_helper_file
from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError, WeChatRuntimeError
from ntchat.utils.singleton import Singleton from ntchat.utils.singleton import Singleton
from ntchat.const import notify_type from ntchat.const import notify_type
from ntchat.utils.logger import get_logger from ntchat.utils.logger import get_logger
@ -31,9 +31,16 @@ class WeChatMgr(metaclass=Singleton):
else: else:
version = wechat_version version = wechat_version
if not is_support_version(version):
raise WeChatVersionNotMatchError(f"ntchat support wechat versions: {','.join(SUPPORT_VERSIONS)}")
if not has_helper_file():
raise WeChatRuntimeError('When using pyinstaller to package exe, you need to add the '
'`--collect-data=ntchat` parameter')
helper_file = get_helper_file(version) helper_file = get_helper_file(version)
if not os.path.exists(helper_file): if not os.path.exists(helper_file):
raise WeChatVersionNotMatchError() raise WeChatRuntimeError("missing core files")
log.info("initialize wechat, version: %s", version) log.info("initialize wechat, version: %s", version)

View File

@ -41,6 +41,17 @@ class ReqData:
return self.__response_message["data"] return self.__response_message["data"]
class RaiseExceptionFunc:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
try:
self.func(*args, **kwargs)
except Exception as e:
log.error('callback error, in function `%s`, error: %s', self.func.__name__, e)
class WeChat: class WeChat:
client_id: int = 0 client_id: int = 0
pid: int = 0 pid: int = 0
@ -51,34 +62,32 @@ class WeChat:
WeChatMgr().append_instance(self) WeChatMgr().append_instance(self)
self.__wait_login_event = Event() self.__wait_login_event = Event()
self.__req_data_cache = {} self.__req_data_cache = {}
self.__msg_event_emitter = pyee.EventEmitter() self.event_emitter = pyee.EventEmitter()
self.__login_info = {} self.__login_info = {}
def on(self, msg_type, f): def on(self, msg_type, f):
return self.__msg_event_emitter.on(str(msg_type), f)
def msg_register(self, msg_type: Union[int, List[int], Tuple[int]]):
if not (isinstance(msg_type, list) or isinstance(msg_type, tuple)): if not (isinstance(msg_type, list) or isinstance(msg_type, tuple)):
msg_type = [msg_type] msg_type = [msg_type]
for event in msg_type:
self.event_emitter.on(str(event), RaiseExceptionFunc(f))
def msg_register(self, msg_type: Union[int, List[int], Tuple[int]]):
def wrapper(f): def wrapper(f):
wraps(f) wraps(f)
for event in msg_type: self.on(msg_type, f)
self.on(event, f)
return f return f
return wrapper return wrapper
def on_close(self): def on_close(self):
self.login_status = False self.login_status = False
self.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 = { message = {
"type": notify_type.MT_RECV_WECHAT_QUIT_MSG, "type": notify_type.MT_RECV_WECHAT_QUIT_MSG,
"data": {} "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): def bind_client_id(self, client_id):
self.status = True self.status = True
@ -102,14 +111,17 @@ class WeChat:
req_data.on_response(message) req_data.on_response(message)
del self.__req_data_cache[extend] del self.__req_data_cache[extend]
else: else:
self.__msg_event_emitter.emit(str(msg_type), self, message) self.event_emitter.emit(str(msg_type), self, message)
self.__msg_event_emitter.emit(str(notify_type.MT_ALL), self, message) self.event_emitter.emit(str(notify_type.MT_ALL), self, message)
def wait_login(self, timeout=None): def wait_login(self, timeout=None):
log.info("wait login...") log.info("wait login...")
self.__wait_login_event.wait(timeout) self.__wait_login_event.wait(timeout)
def open(self, smart=False): def open(self, smart=False, show_login_qrcode=False):
if show_login_qrcode:
wcprobe.show_login_qrcode()
self.pid = wcprobe.open(smart) self.pid = wcprobe.open(smart)
log.info("open wechat pid: %d", self.pid) log.info("open wechat pid: %d", self.pid)
return self.pid != 0 return self.pid != 0
@ -457,3 +469,23 @@ class WeChat:
"room_wxid": room_wxid "room_wxid": room_wxid
} }
return self.__send(send_type.MT_QUIT_DEL_ROOM_MSG, data) 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): class WeChatNotLoginError(Exception):
pass pass
class WeChatRuntimeError(Exception):
pass

View File

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

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.8.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe -m pip install --upgrade pip setuptools wheel %UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe -m pip install --upgrade pip setuptools wheel %UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0-win32\python.exe -m pip install --upgrade pip setuptools wheel
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"
pushd .. pushd ..
%UserProfile%\.pyenv\pyenv-win\versions\3.6.0-win32\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.6.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
@ -10,4 +11,5 @@ pushd ..
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0-win32\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.8.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.9.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.10.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0-win32\python.exe setup.py bdist_wheel -d wheelhouse
popd popd

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.8.0\python.exe -m pip install --upgrade pip setuptools wheel
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe -m pip install --upgrade pip setuptools wheel %UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe -m pip install --upgrade pip setuptools wheel
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe -m pip install --upgrade pip setuptools wheel %UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe -m pip install --upgrade pip setuptools wheel
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0\python.exe -m pip install --upgrade pip setuptools wheel
call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
pushd .. pushd ..
%UserProfile%\.pyenv\pyenv-win\versions\3.6.0\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.6.0\python.exe setup.py bdist_wheel -d wheelhouse
@ -10,4 +11,5 @@ pushd ..
%UserProfile%\.pyenv\pyenv-win\versions\3.8.0\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.8.0\python.exe setup.py bdist_wheel -d wheelhouse
%UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.9.0\python.exe setup.py bdist_wheel -d wheelhouse
%UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe setup.py bdist_wheel -d wheelhouse %UserProfile%\.pyenv\pyenv-win\versions\3.10.0\python.exe setup.py bdist_wheel -d wheelhouse
%UserProfile%\.pyenv\pyenv-win\versions\3.11.0\python.exe setup.py bdist_wheel -d wheelhouse
popd popd

View File

@ -194,7 +194,7 @@ extension.extra_compile_cpp_args = extra_compile_cpp_args[target_os]
setup( setup(
name='ntchat', name='ntchat',
version='0.1.12', version='0.1.17',
description='About Conversational RPA SDK for Chatbot Makers', description='About Conversational RPA SDK for Chatbot Makers',
long_description="", long_description="",
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
@ -209,7 +209,8 @@ setup(
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10' 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11'
], ],
package_data={"": ["py.typed", "*.pyi", "helper*.dat"]}, package_data={"": ["py.typed", "*.pyi", "helper*.dat"]},
include_package_data=False, include_package_data=False,