From 29c5846c12ee7b9672a482f6070ed7354bfcd82e Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 25 Aug 2022 00:57:05 +0800 Subject: [PATCH 01/62] Delete test.py --- examples/test.py | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 examples/test.py 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 From bc17f109249da32f6cd3bac0d2ca2155d8196903 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 25 Aug 2022 12:37:49 +0800 Subject: [PATCH 02/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8D=E8=83=BD?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=B7=B2=E7=BB=8F=E6=89=93=E5=BC=80=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- README.md | 8 ++++---- examples/auto_accept_friend_request.py | 2 +- examples/echo_bot_msg_register.py | 3 ++- examples/echo_bot_on.py | 2 +- examples/get_contacts.py | 2 +- examples/get_rooms.py | 2 +- examples/multi_open.py | 2 +- examples/send_text.py | 2 +- examples/send_text_ui.py | 2 +- ntchat/conf/__init__.py | 2 +- setup.py | 2 +- 12 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 8d9bcf1..a13f197 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ build/ config.ini ntchat/wc/*.pyd wheelhouse/ -setup_conf.py \ No newline at end of file +setup_conf.py +upload.bat \ No newline at end of file diff --git a/README.md b/README.md index a38a16f..7d242ac 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ import ntchat wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 等待登录 wechat.wait_login() @@ -63,7 +63,7 @@ import ntchat wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 等待登录 wechat.wait_login() @@ -97,7 +97,7 @@ import ntchat wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 注册消息回调 @@ -155,7 +155,7 @@ 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, _): diff --git a/examples/auto_accept_friend_request.py b/examples/auto_accept_friend_request.py index 0d29212..1695f39 100644 --- a/examples/auto_accept_friend_request.py +++ b/examples/auto_accept_friend_request.py @@ -6,7 +6,7 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 注册消息回调 diff --git a/examples/echo_bot_msg_register.py b/examples/echo_bot_msg_register.py index 685a703..4e3d012 100644 --- a/examples/echo_bot_msg_register.py +++ b/examples/echo_bot_msg_register.py @@ -5,7 +5,8 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) + +wechat.open(smart=True) # 注册消息回调 diff --git a/examples/echo_bot_on.py b/examples/echo_bot_on.py index 980e43d..7703197 100644 --- a/examples/echo_bot_on.py +++ b/examples/echo_bot_on.py @@ -5,7 +5,7 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) def on_recv_text_msg(wechat_instance: ntchat.WeChat, message): diff --git a/examples/get_contacts.py b/examples/get_contacts.py index 9c17c69..372ee30 100644 --- a/examples/get_contacts.py +++ b/examples/get_contacts.py @@ -5,7 +5,7 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 等待登录 wechat.wait_login() diff --git a/examples/get_rooms.py b/examples/get_rooms.py index 986bc2c..644f93d 100644 --- a/examples/get_rooms.py +++ b/examples/get_rooms.py @@ -5,7 +5,7 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 等待登录 wechat.wait_login() 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/send_text.py b/examples/send_text.py index b6932e3..0f217dc 100644 --- a/examples/send_text.py +++ b/examples/send_text.py @@ -5,7 +5,7 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 -wechat.open(smart=False) +wechat.open(smart=True) # 等待登录 wechat.wait_login() diff --git a/examples/send_text_ui.py b/examples/send_text_ui.py index 64543c8..5504ec7 100644 --- a/examples/send_text_ui.py +++ b/examples/send_text_ui.py @@ -24,7 +24,7 @@ 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, _): diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 8d76f25..856ce1d 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1 +1 @@ -VERSION = '0.1.1' +VERSION = '0.1.2' diff --git a/setup.py b/setup.py index b5b7aa3..7da4a00 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.2', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From d8ecba28b9b29ef28ea244617d5e2614b9893851 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 25 Aug 2022 12:39:13 +0800 Subject: [PATCH 03/62] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA0.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d242ac..889419d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

NtChat

- release + release License

From 0fc8d219204b74ba56f5857d976723e276dfaa4f Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 25 Aug 2022 14:06:46 +0800 Subject: [PATCH 04/62] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/resources/send_text_ui.jpg | Bin 19183 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/resources/send_text_ui.jpg diff --git a/examples/resources/send_text_ui.jpg b/examples/resources/send_text_ui.jpg deleted file mode 100644 index 8ee3ac24d3aed6a8a34127cadd4bb77c40eab03c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19183 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
  • q7Idlcr9GSs{Xqy#yexER)d_cB=4m?zk^QK3~dHCo@6e6xb!c+ z^f+N%dZzFT^stKPv1Eil+%ai;7eMSbKmV{CKt^mzA%N;_NUj$)=d172i&rQ`DtDMY zy5haRTLCxs6~MWALON7n^v>@nh;zAF_$zJWZGUC=Wx~sKl!t}LyTGBcuWgq?T@O_P zc7d-2hor(OgTe*P!tF|*+rJPyT=bP^UPCl|)1lH_j*u*TSDi+Y{a`pI{T=9W)L%LAL9Q`bOsSi=buU^ln$Bx8t+i%0D!4(s<1Lhr$-Y!{1dGAnd?H#(FC3zAkRsjvua3m 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 W8)wudm-B7o|2xy^<9q*l>iK`jmj50A From 7e1bf189ee671b17568258ca3cc40a88cd44063b Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 25 Aug 2022 14:18:47 +0800 Subject: [PATCH 05/62] =?UTF-8?q?=E4=BF=AE=E6=94=B9echo=5Fbot=E4=BE=8B?= =?UTF-8?q?=E5=AD=90=E4=B8=8D=E5=9B=9E=E5=A4=8D=E7=BE=A4=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/echo_bot_msg_register.py | 5 +++-- examples/echo_bot_on.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/echo_bot_msg_register.py b/examples/echo_bot_msg_register.py index 4e3d012..41de497 100644 --- a/examples/echo_bot_msg_register.py +++ b/examples/echo_bot_msg_register.py @@ -15,9 +15,10 @@ 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']}") diff --git a/examples/echo_bot_on.py b/examples/echo_bot_on.py index 7703197..34be141 100644 --- a/examples/echo_bot_on.py +++ b/examples/echo_bot_on.py @@ -12,9 +12,10 @@ 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']}") From c261a9bdf56f30a7f701d59a6e6a2862df39978c Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 25 Aug 2022 14:26:33 +0800 Subject: [PATCH 06/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/auto_accept_friend_request.py | 5 ++++- examples/echo_bot_msg_register.py | 4 +++- examples/echo_bot_on.py | 4 +++- examples/get_contacts.py | 5 ++++- examples/get_rooms.py | 5 ++++- examples/send_text.py | 5 ++++- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/examples/auto_accept_friend_request.py b/examples/auto_accept_friend_request.py index 1695f39..6af0f36 100644 --- a/examples/auto_accept_friend_request.py +++ b/examples/auto_accept_friend_request.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import time import ntchat import xml.dom.minidom @@ -24,9 +25,11 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message): wechat_instance.accept_friend_request(encryptusername, ticket, int(scene)) +# 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码 try: while True: - pass + 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 41de497..6bd29ec 100644 --- a/examples/echo_bot_msg_register.py +++ b/examples/echo_bot_msg_register.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import time import ntchat wechat = ntchat.WeChat() @@ -22,9 +23,10 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message): 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 34be141..32ccf54 100644 --- a/examples/echo_bot_on.py +++ b/examples/echo_bot_on.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import time import ntchat wechat = ntchat.WeChat() @@ -22,9 +23,10 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message): # 监听接收文本消息 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 372ee30..7a6e1c1 100644 --- a/examples/get_contacts.py +++ b/examples/get_contacts.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import time import ntchat wechat = ntchat.WeChat() @@ -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_rooms.py b/examples/get_rooms.py index 644f93d..f187f7c 100644 --- a/examples/get_rooms.py +++ b/examples/get_rooms.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import time import ntchat wechat = ntchat.WeChat() @@ -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/send_text.py b/examples/send_text.py index 0f217dc..536aa3c 100644 --- a/examples/send_text.py +++ b/examples/send_text.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import sys +import time import ntchat wechat = ntchat.WeChat() @@ -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() + From 0cd080dcafb14af473dd9c3c8090830a18321e83 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sat, 27 Aug 2022 11:36:07 +0800 Subject: [PATCH 07/62] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BE=A4=E6=88=90=E5=91=98=E4=B8=BA=E5=A5=BD=E5=8F=8B=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/echo_bot_image.py | 32 +++++++++++++++ examples/echo_bot_msg_register.py | 1 - examples/send_text_ui.py | 2 + ntchat/__init__.py | 12 +++++- ntchat/conf/__init__.py | 10 ++++- ntchat/const/notify_type.py | 66 +++++++++++++++++++++++++++++++ ntchat/const/send_type.py | 65 ++++++++++++++++++++++++++++++ ntchat/const/wx_type.py | 50 ----------------------- ntchat/core/mgr.py | 9 +++-- ntchat/core/wechat.py | 59 ++++++++++++++++----------- ntchat/utils/logger.py | 33 +++++----------- setup.py | 2 +- 13 files changed, 236 insertions(+), 107 deletions(-) create mode 100644 examples/echo_bot_image.py create mode 100644 ntchat/const/notify_type.py create mode 100644 ntchat/const/send_type.py delete mode 100644 ntchat/const/wx_type.py diff --git a/README.md b/README.md index 889419d..0831dd4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    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 6bd29ec..84bf82b 100644 --- a/examples/echo_bot_msg_register.py +++ b/examples/echo_bot_msg_register.py @@ -6,7 +6,6 @@ wechat = ntchat.WeChat() # 打开pc微信, smart: 是否管理已经登录的微信 - wechat.open(smart=True) diff --git a/examples/send_text_ui.py b/examples/send_text_ui.py index 5504ec7..daa3842 100644 --- a/examples/send_text_ui.py +++ b/examples/send_text_ui.py @@ -25,6 +25,8 @@ def __init__(self): def on_btn_open_clicked(self, sender, _): self.wechat_instance = ntchat.WeChat() self.wechat_instance.open(smart=True) + + # 监听所有通知消息 self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message) def on_btn_send_clicked(self, sender, _): diff --git a/ntchat/__init__.py b/ntchat/__init__.py index 437b2cb..b641727 100644 --- a/ntchat/__init__.py +++ b/ntchat/__init__.py @@ -1,9 +1,19 @@ 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 + +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 + + exit_ = wcprobe.exit diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 856ce1d..9cab2c0 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1 +1,9 @@ -VERSION = '0.1.2' +VERSION = '0.1.3' + +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..0d321b0 --- /dev/null +++ b/ntchat/const/notify_type.py @@ -0,0 +1,66 @@ +# 用于接收所有的通知消息 +MT_ALL = 11000 + +# 第个通知消息,此时已经托管上微信 +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 + + + diff --git a/ntchat/const/send_type.py b/ntchat/const/send_type.py new file mode 100644 index 0000000..6a9032c --- /dev/null +++ b/ntchat/const/send_type.py @@ -0,0 +1,65 @@ +# 获取自己的帐号信息 +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 + +# 发送gif消息 +MT_SEND_GIF_MSG = 11043 + +# 接受新好友请求 +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 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..192de18 100644 --- a/ntchat/core/mgr.py +++ b/ntchat/core/mgr.py @@ -4,8 +4,9 @@ from ntchat.utils.xdg import get_helper_file from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError 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) @@ -62,7 +63,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) diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 921d71a..c9ebfa9 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 @@ -72,12 +72,12 @@ 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) @@ -87,7 +87,7 @@ def on_recv(self, 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.__msg_event_emitter.emit(str(notify_type.MT_ALL), self, message) def wait_login(self, timeout=None): log.info("wait login...") @@ -147,25 +147,25 @@ 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_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 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_members(self, room_wxid: str): """ @@ -174,7 +174,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 +184,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 +195,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 +205,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 +218,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 +228,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 +238,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 +249,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 +259,7 @@ 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 accept_friend_request(self, encryptusername: str, ticket: str, scene: int): """ @@ -270,13 +270,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 +286,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 +296,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 +306,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 +316,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 +326,17 @@ 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_NAME_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) diff --git a/ntchat/utils/logger.py b/ntchat/utils/logger.py index 09d5477..f5675a5 100644 --- a/ntchat/utils/logger.py +++ b/ntchat/utils/logger.py @@ -1,42 +1,27 @@ -import logging import os -import configparser +import logging 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) +from .xdg import get_log_dir +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] + if conf.LOG_FILE_KEY in os.environ: + filepath = os.environ[conf.LOG_FILE_KEY] else: base_dir = get_log_dir() if not os.path.exists(base_dir): @@ -48,13 +33,13 @@ def get_logger(name: str) -> logging.Logger: 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.setLevel(log_level) 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/setup.py b/setup.py index 7da4a00..e7b2202 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.2', + version='0.1.3', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 594cc561cb17bb03d4f32b79ccda123ecdaf9b45 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sat, 27 Aug 2022 14:28:03 +0800 Subject: [PATCH 08/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E9=80=80=E5=87=BA=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ntchat/const/notify_type.py | 3 +++ ntchat/core/mgr.py | 6 ++---- ntchat/core/wechat.py | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ntchat/const/notify_type.py b/ntchat/const/notify_type.py index 0d321b0..7220f38 100644 --- a/ntchat/const/notify_type.py +++ b/ntchat/const/notify_type.py @@ -1,6 +1,9 @@ # 用于接收所有的通知消息 MT_ALL = 11000 +# 微信进程退出通知 +MT_RECV_WECHAT_QUIT_MSG = 11001 + # 第个通知消息,此时已经托管上微信 MT_READY_MSG = 11024 diff --git a/ntchat/core/mgr.py b/ntchat/core/mgr.py index 192de18..4a7a943 100644 --- a/ntchat/core/mgr.py +++ b/ntchat/core/mgr.py @@ -49,8 +49,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 @@ -71,5 +70,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 c9ebfa9..a2445b8 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -68,6 +68,15 @@ def wrapper(f): return f return wrapper + def on_close(self): + self.login_status = False + self.status = False + self.__msg_event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self) + + 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"] From 9505e697795c517ec03956106553a54cabaf6b98 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sat, 27 Aug 2022 20:30:03 +0800 Subject: [PATCH 09/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=80=E5=87=BA?= =?UTF-8?q?=E4=BE=8B=E5=AD=90=EF=BC=8C=E5=8F=91=E5=B8=830.1.4=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/quit_event.py | 38 ++++++++++++++++++++++++++++++++++++++ ntchat/conf/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 examples/quit_event.py diff --git a/README.md b/README.md index 0831dd4..19945e3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    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/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 9cab2c0..cf6db60 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.3' +VERSION = '0.1.4' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/setup.py b/setup.py index e7b2202..1d4cc26 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.3', + version='0.1.4', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 708cc9e2d9bb8d3500d2f29f1334ef6ad03e3f75 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 29 Aug 2022 17:02:06 +0800 Subject: [PATCH 10/62] =?UTF-8?q?=E6=96=B0=E5=A2=9Eweb=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 + fastapi_example/README.md | 26 ++++ fastapi_example/__init__.py | 0 fastapi_example/down.py | 17 +++ fastapi_example/exception.py | 8 ++ fastapi_example/main.py | 240 +++++++++++++++++++++++++++++++ fastapi_example/mgr.py | 55 +++++++ fastapi_example/models.py | 134 +++++++++++++++++ fastapi_example/requirements.txt | 4 + fastapi_example/utils.py | 6 + fastapi_example/xdg.py | 14 ++ ntchat/core/wechat.py | 2 +- 12 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 fastapi_example/README.md create mode 100644 fastapi_example/__init__.py create mode 100644 fastapi_example/down.py create mode 100644 fastapi_example/exception.py create mode 100644 fastapi_example/main.py create mode 100644 fastapi_example/mgr.py create mode 100644 fastapi_example/models.py create mode 100644 fastapi_example/requirements.txt create mode 100644 fastapi_example/utils.py create mode 100644 fastapi_example/xdg.py diff --git a/README.md b/README.md index 19945e3..54a5e9f 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,10 @@ except KeyboardInterrupt: sys.exit() ``` +## 使用fastapi框架实现的web api接口 + +[查看fastapi_example例子](./fastapi_example/README.md) + ## 使用pyxcgui界面库实现的简单例子 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接口 +![vfazT0.jpg](https://s1.ax1x.com/2022/08/29/vfazT0.jpg) + +## 安装依赖 +```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..8fb9699 --- /dev/null +++ b/fastapi_example/down.py @@ -0,0 +1,17 @@ +import os.path +import time +import requests +from xdg import get_download_dir +from models import SendMediaReqModel + + +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 = os.path.join(get_download_dir(), str(time.time_ns())) + 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..d2a637e --- /dev/null +++ b/fastapi_example/exception.py @@ -0,0 +1,8 @@ +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..a21fc1a --- /dev/null +++ b/fastapi_example/main.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +import uvicorn +from functools import wraps +from fastapi import FastAPI +from mgr import ClientManager +from typing import List +from down import get_local_path +from exception import MediaNotExistsError, ClientNotExists +import models +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): + ret = client_mgr.get_client(model.guid).open(model.smart) + return response_json(1 if ret else 0) + + +@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() + print(data) + return response_json(1, data) + + +@app.post("/contact/get_contact_detail", summary="获取指定联系人详细信息", tags=["Contact"], + response_model=models.ContactDetailModel) +@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("/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_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("/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) + + +if __name__ == '__main__': + uvicorn.run(app=app) diff --git a/fastapi_example/mgr.py b/fastapi_example/mgr.py new file mode 100644 index 0000000..8b3af7b --- /dev/null +++ b/fastapi_example/mgr.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +import ntchat +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 = "" + + +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) + return guid + + def get_client(self, guid: str) -> Union[None, ntchat.WeChat]: + 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, message): + if not self.callback_url: + return + + client_message = { + "guid": wechat.guid, + "message": message + } + requests.post(self.callback_url, json=client_message) diff --git a/fastapi_example/models.py b/fastapi_example/models.py new file mode 100644 index 0000000..83d1425 --- /dev/null +++ b/fastapi_example/models.py @@ -0,0 +1,134 @@ +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 CreateRoomReqModel(ClientReqModel): + member_list: List[str] + + +class RoomMembersReqModel(CreateRoomReqModel): + room_wxid: str + + +class AddRoomFriendReqModel(ClientReqModel): + room_wxid: str + wxid: str + verify: str + + +class ModifyRoomNameReqModel(ClientReqModel): + room_wxid: str + 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] = "" 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..f4c6331 --- /dev/null +++ b/fastapi_example/xdg.py @@ -0,0 +1,14 @@ +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') + if not os.path.isdir(user_dir): + os.makedirs(user_dir) + return user_dir diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index a2445b8..9aef999 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -335,7 +335,7 @@ def modify_room_notice(self, room_wxid: str, notice: str): "room_wxid": room_wxid, "notice": notice } - return self.__send_sync(send_type.MT_MOD_ROOM_NAME_MSG, data) + return self.__send_sync(send_type.MT_MOD_ROOM_NOTICE_MSG, data) def add_room_friend(self, room_wxid: str, wxid: str, verify: str): """ From 1e7d0350d5950d789473cb1fab3805c9d06723dc Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 29 Aug 2022 17:48:33 +0800 Subject: [PATCH 11/62] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/exception.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fastapi_example/exception.py b/fastapi_example/exception.py index d2a637e..d80b757 100644 --- a/fastapi_example/exception.py +++ b/fastapi_example/exception.py @@ -1,5 +1,6 @@ class ClientNotExists(Exception): guid = "" + def __init__(self, guid): self.guid = guid From a95b6ad2dc2088fca7ad425e476c9061ea293080 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 12:42:49 +0800 Subject: [PATCH 12/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dpyinstaller=E6=89=93?= =?UTF-8?q?=E5=8C=85=E9=97=AE=E9=A2=98,=E5=8F=91=E5=B8=830.1.5=E7=89=88?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 2 +- ntchat/conf/__init__.py | 2 +- ntchat/utils/xdg.py | 2 +- setup.py | 4 ++-- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index a13f197..cde6f02 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ main.spec build/ config.ini ntchat/wc/*.pyd +ntchat/wc/*.dat wheelhouse/ setup_conf.py upload.bat \ No newline at end of file diff --git a/README.md b/README.md index 54a5e9f..f0663e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index cf6db60..71adb14 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.4' +VERSION = '0.1.5' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/utils/xdg.py b/ntchat/utils/xdg.py index 339972e..7825678 100644 --- a/ntchat/utils/xdg.py +++ b/ntchat/utils/xdg.py @@ -23,7 +23,7 @@ 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(): diff --git a/setup.py b/setup.py index 1d4cc26..23484d6 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.4', + version='0.1.5', 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', From 4391ad51b1cc74e277a9bef2eaeb555c5422d4dc Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 15:09:53 +0800 Subject: [PATCH 13/62] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=80=80=E5=87=BA?= =?UTF-8?q?=E7=BE=A4=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/main.py | 8 ++++++++ fastapi_example/models.py | 5 ++++- ntchat/core/wechat.py | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index a21fc1a..ee143f6 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -161,6 +161,14 @@ async def add_room_friend(model: models.ModifyRoomNameReqModel): 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): diff --git a/fastapi_example/models.py b/fastapi_example/models.py index 83d1425..7db4304 100644 --- a/fastapi_example/models.py +++ b/fastapi_example/models.py @@ -101,8 +101,11 @@ class AddRoomFriendReqModel(ClientReqModel): verify: str -class ModifyRoomNameReqModel(ClientReqModel): +class RoomReqModel(ClientReqModel): room_wxid: str + + +class ModifyRoomNameReqModel(RoomReqModel): name: str diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 9aef999..8a94790 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -349,3 +349,11 @@ def add_room_friend(self, room_wxid: str, wxid: str, verify: str): } 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) From 8fc25929268e451e057cd53beeceb0873c6b524f Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 16:10:10 +0800 Subject: [PATCH 14/62] =?UTF-8?q?=E6=96=B0=E5=A2=9Ecmd=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/close_log1.py | 34 +++++++++++++++++++++++++++ examples/close_log2.py | 33 ++++++++++++++++++++++++++ examples/cmd_close_event.py | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 examples/close_log1.py create mode 100644 examples/close_log2.py create mode 100644 examples/cmd_close_event.py diff --git a/examples/close_log1.py b/examples/close_log1.py new file mode 100644 index 0000000..e809e97 --- /dev/null +++ b/examples/close_log1.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/close_log2.py b/examples/close_log2.py new file mode 100644 index 0000000..ce4381e --- /dev/null +++ b/examples/close_log2.py @@ -0,0 +1,33 @@ +import sys +import time +import logging +logging.disable(logging.INFO) + +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() From a1864a04564cbf9d044c7cb99582b3bd3380b3ad Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 16:24:57 +0800 Subject: [PATCH 15/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f0663e1..5071e39 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,12 @@ except KeyboardInterrupt: ## 使用fastapi框架实现的web api接口 +通过fastapi的swagger在线文档可以很方便的管理NtChat接口 + [查看fastapi_example例子](./fastapi_example/README.md) +![vfazT0.jpg](https://s1.ax1x.com/2022/08/29/vfazT0.jpg) + ## 使用pyxcgui界面库实现的简单例子 From 61aa8ed0fcbda5bdc0a6a58f3f4c1706d0fe9ad2 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 16:33:05 +0800 Subject: [PATCH 16/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5071e39..7bcc758 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ except KeyboardInterrupt: 通过fastapi的swagger在线文档可以很方便的管理NtChat接口 -[查看fastapi_example例子](./fastapi_example/README.md) +[查看fastapi_example例子](./fastapi_example) ![vfazT0.jpg](https://s1.ax1x.com/2022/08/29/vfazT0.jpg) From a4be63cd03c10ed6eabc5ac48eae3b80be5e8f64 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 17:04:08 +0800 Subject: [PATCH 17/62] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B8=B8=E8=A7=81?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++ docs/FAQ.md | 66 ++++++++++++++++++++++++++++++++++++ examples/msg_register_all.py | 25 ++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 docs/FAQ.md create mode 100644 examples/msg_register_all.py diff --git a/README.md b/README.md index 7bcc758..4f6e8fe 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ ## 支持的微信版本下载 - [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe) +## 帮助文档 +[常见问题](docs/FAQ.md) + ## 安装 ```bash diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 0000000..6943df3 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,66 @@ +## 如何多开 + +新建多个ntchat.WeChat实例,然后调用open方法: +```python +import ntchat + +# 多开3个微信 +for i in range(3): + wechat = ntchat.WeChat() + wechat.open(smart=False) +``` +更完善的多实例管理查看[fastapi_example例子](./fastapi_example) + +## 如何监听输出所有的消息 +```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) + +## 如何关闭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 +``` + +## 如何正常的关闭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) +``` + + +## pyinstaller打包exe +使用pyinstaller打包NtChat项目,需要添加`--collect-data=ntchat`选项 + +打包成单个exe程序 +```bash +pyinstaller -F --collect-data=ntchat main.py +``` + +将所有的依赖文件打包到一个目录中 +```bash +pyinstaller -y --collect-data=ntchat main.py +``` 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() From 1ed2f6bd7ff6ac89ac35d86e5934cde8b9936901 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 17:05:08 +0800 Subject: [PATCH 18/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f6e8fe..be1b542 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe) ## 帮助文档 -[常见问题](docs/FAQ.md) +- [常见问题](docs/FAQ.md) ## 安装 From f8906a8934f09fa873de7a5bbc55a33aaefa0e7a Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 17:06:35 +0800 Subject: [PATCH 19/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index be1b542..f1f43bd 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ - [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe) ## 帮助文档 -- [常见问题](docs/FAQ.md) +- 查看[常见问题](docs/FAQ.md) +- 加入群聊 [PyXCGUI&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI) ## 安装 From c9179ed6295e4aa36553e9cefcefd3e9db5b0865 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 30 Aug 2022 17:08:24 +0800 Subject: [PATCH 20/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1f43bd..3e6ba8b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe) ## 帮助文档 -- 查看[常见问题](docs/FAQ.md) +- 查看 [常见问题](docs/FAQ.md) - 加入群聊 [PyXCGUI&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI) ## 安装 From 8fc68a4bac19f25bc36d90496a2f5dd0185d9d89 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 31 Aug 2022 12:18:25 +0800 Subject: [PATCH 21/62] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E6=96=87=E4=BB=B6log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ntchat/utils/logger.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/ntchat/utils/logger.py b/ntchat/utils/logger.py index f5675a5..35d8227 100644 --- a/ntchat/utils/logger.py +++ b/ntchat/utils/logger.py @@ -1,7 +1,5 @@ import os import logging -from datetime import datetime -from .xdg import get_log_dir from .. import conf @@ -19,24 +17,6 @@ def get_logger(name: str) -> logging.Logger: logger.setLevel(log_level) logger.propagate = False - # create file handler and set level to debug - if conf.LOG_FILE_KEY in os.environ: - filepath = os.environ[conf.LOG_FILE_KEY] - else: - base_dir = get_log_dir() - if not os.path.exists(base_dir): - os.mkdir(base_dir) - - time_now = datetime.now() - time_format = '%Y-%m-%d-%H-%M' - - filepath = f'{base_dir}/log-{time_now.strftime(time_format)}.txt' - - file_handler = logging.FileHandler(filepath, 'a', encoding='utf-8') - file_handler.setLevel(log_level) - file_handler.setFormatter(log_formatter) - logger.addHandler(file_handler) - # create console handler and set level to info console_handler = logging.StreamHandler() console_handler.setLevel(log_level) From 1f8aa499722fec38221cc9b376260f9dbcdace06 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 31 Aug 2022 14:24:30 +0800 Subject: [PATCH 22/62] =?UTF-8?q?=E5=8F=91=E5=B8=830.1.6=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- ntchat/conf/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3e6ba8b..c68c077 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 71adb14..d55ea8c 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.5' +VERSION = '0.1.6' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/setup.py b/setup.py index 23484d6..1b61e36 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.5', + version='0.1.6', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 3ac176ab2d1f38b113a275cd402e473180a9932d Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 31 Aug 2022 18:53:31 +0800 Subject: [PATCH 23/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c68c077..6005381 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ ## 帮助文档 - 查看 [常见问题](docs/FAQ.md) - 加入群聊 [PyXCGUI&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI) +- 查看 [PyXCGUI项目](https://github.com/smallevilbeast/pyxcgui) ## 安装 From de9fee4682e6dc6901e784089a3bc3794405ec9c Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 1 Sep 2022 09:22:21 +0800 Subject: [PATCH 24/62] =?UTF-8?q?FAQ=E6=B7=BB=E5=8A=A0WeChatVersionNotMatc?= =?UTF-8?q?hError=E5=BC=82=E5=B8=B8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/FAQ.md b/docs/FAQ.md index 6943df3..f242d48 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,3 +1,11 @@ +## WeChatVersionNotMatchError异常 +如果出现`ntchat.exception.WeChatVersionNotMatchError`异常, 请确认是否安装github上指定的微信版本,如果确认已经安装,还是报错,可以在代码中添加以下代码,跳过微信版本检测 +```python +import ntchat +ntchat.set_wechat_exe_path(wechat_version='3.6.0.18') +``` + + ## 如何多开 新建多个ntchat.WeChat实例,然后调用open方法: From dd054f1c8b457e886efb28aae6129fd239cfdb70 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 1 Sep 2022 14:50:26 +0800 Subject: [PATCH 25/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0fastapi=E4=BE=8B?= =?UTF-8?q?=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/mgr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fastapi_example/mgr.py b/fastapi_example/mgr.py index 8b3af7b..a8553a4 100644 --- a/fastapi_example/mgr.py +++ b/fastapi_example/mgr.py @@ -32,6 +32,7 @@ def create_client(self): # 注册回调 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) -> Union[None, ntchat.WeChat]: @@ -53,3 +54,6 @@ def __on_callback(self, wechat, message): "message": message } requests.post(self.callback_url, json=client_message) + + def __on_quit_callback(self, wechat): + self.__on_callback(wechat, {}) From f0f2e445f2825bff04b2cb1083a9c78f0f9a3eff Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sun, 4 Sep 2022 17:47:20 +0800 Subject: [PATCH 26/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dfastapi=E9=80=80?= =?UTF-8?q?=E5=87=BA=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/mgr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi_example/mgr.py b/fastapi_example/mgr.py index a8553a4..7be9b6b 100644 --- a/fastapi_example/mgr.py +++ b/fastapi_example/mgr.py @@ -56,4 +56,4 @@ def __on_callback(self, wechat, message): requests.post(self.callback_url, json=client_message) def __on_quit_callback(self, wechat): - self.__on_callback(wechat, {}) + self.__on_callback(wechat, {"type": ntchat.MT_RECV_WECHAT_QUIT_MSG, "data": {}}) From 0c19723833731d429e7fddbd27931ade91a0c8ff Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sun, 4 Sep 2022 17:50:37 +0800 Subject: [PATCH 27/62] =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E9=80=80=E5=87=BA?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5MT=5FALL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ntchat/core/wechat.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 8a94790..4181809 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -73,6 +73,12 @@ def on_close(self): self.status = False self.__msg_event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self) + message = { + "type": notify_type.MT_RECV_WECHAT_QUIT_MSG, + "data": {} + } + self.__msg_event_emitter.emit(str(notify_type.MT_ALL), self, message) + def bind_client_id(self, client_id): self.status = True self.client_id = client_id From f326669603a6def999023e2195e4de103d3a75ca Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sun, 4 Sep 2022 20:40:45 +0800 Subject: [PATCH 28/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A8=A1=E7=B3=8A?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E8=81=94=E7=B3=BB=E4=BA=BA=E5=92=8C=E5=8F=91?= =?UTF-8?q?=E9=80=81xml=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/search_contacts.py | 46 ++++++++++++++++++++++ ntchat/conf/__init__.py | 2 +- ntchat/const/notify_type.py | 17 ++++++++ ntchat/const/send_type.py | 11 +++++- ntchat/core/wechat.py | 77 +++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 7 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 examples/search_contacts.py diff --git a/README.md b/README.md index 6005381..8dbcc25 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    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/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index d55ea8c..685af60 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.6' +VERSION = '0.1.7' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/const/notify_type.py b/ntchat/const/notify_type.py index 7220f38..e1d017f 100644 --- a/ntchat/const/notify_type.py +++ b/ntchat/const/notify_type.py @@ -65,5 +65,22 @@ # 未知应用消息通知 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 index 6a9032c..05b8c5a 100644 --- a/ntchat/const/send_type.py +++ b/ntchat/const/send_type.py @@ -11,7 +11,10 @@ MT_GET_ROOM_MEMBERS_MSG = 11032 # 获取指定联系人的详细信息 -MT_GET_CONTACT_DETAIL_MSG = 11034 +MT_GET_CONTACT_DETAIL_MSG = 11029 + +# 获取指定群的详细信息 +MT_GET_ROOM_DETAIL_MSG = 11125 # 发送文本消息 MT_SEND_TEXT_MSG = 11036 @@ -37,6 +40,9 @@ # 发送gif消息 MT_SEND_GIF_MSG = 11043 +# 发送xml消息 +MT_SEND_XML_MSG = 11113 + # 接受新好友请求 MT_ACCEPT_FRIEND_MSG = 11065 @@ -63,3 +69,6 @@ # 添加群成员为好友 MT_ADD_FRIEND_MSG = 11062 + +# 数据库查询 +MT_SQL_QUERY_MSG = 11027 diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 4181809..ec0cdee 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -66,6 +66,7 @@ def wrapper(f): for event in msg_type: self.on(event, f) return f + return wrapper def on_close(self): @@ -152,6 +153,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): """ 获取登录信息 @@ -171,17 +182,72 @@ def get_contacts(self): return self.__send_sync(send_type.MT_GET_CONTACTS_MSG) def get_contact_detail(self, wxid): + """ + 获取联系人详细信息 + """ data = { "wxid": wxid } 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): + """ + 根据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 = [] + for k, v in conds.items(): + cond_pairs.append(f"{k} like '%{v}%'") + + cond_str = " or ".join(cond_pairs) + sql = f"select username from contact where {cond_str}" + message = self.sql_query(sql, 1) + print(message) + if not message: + return [] + + 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(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): """ 获取群成员列表 @@ -276,6 +342,17 @@ def send_gif(self, to_wxid, file): } 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 accept_friend_request(self, encryptusername: str, ticket: str, scene: int): """ 同意加好友请求 diff --git a/setup.py b/setup.py index 1b61e36..872cdfd 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.6', + version='0.1.7', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 88a6c75f149780f1fa9239ff4392fae721be8769 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sun, 4 Sep 2022 23:57:01 +0800 Subject: [PATCH 29/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dfastapi=5Fexample?= =?UTF-8?q?=E4=BE=8B=E5=AD=90get=5Fcontact=5Fdetail=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=9A=84=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index ee143f6..939e384 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -87,7 +87,7 @@ async def get_contacts(model: models.ClientReqModel): @app.post("/contact/get_contact_detail", summary="获取指定联系人详细信息", tags=["Contact"], - response_model=models.ContactDetailModel) + 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) From 7b2db4409efb0a6e9a3091abecfcb94480fbde64 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 5 Sep 2022 00:23:29 +0800 Subject: [PATCH 30/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=96=87=E6=9C=AC=E6=B6=88=E6=81=AF=E4=BE=8B?= =?UTF-8?q?=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/schedule_send_text.py | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 examples/schedule_send_text.py 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() + + + + From 16369dfea9fdac6df8da02698d35ecf4e796c6cb Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 5 Sep 2022 09:48:14 +0800 Subject: [PATCH 31/62] =?UTF-8?q?=E5=8E=BB=E9=99=A4close=5Flog2.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/{close_log1.py => close_log.py} | 0 examples/close_log2.py | 33 ------------------------ 2 files changed, 33 deletions(-) rename examples/{close_log1.py => close_log.py} (100%) delete mode 100644 examples/close_log2.py diff --git a/examples/close_log1.py b/examples/close_log.py similarity index 100% rename from examples/close_log1.py rename to examples/close_log.py diff --git a/examples/close_log2.py b/examples/close_log2.py deleted file mode 100644 index ce4381e..0000000 --- a/examples/close_log2.py +++ /dev/null @@ -1,33 +0,0 @@ -import sys -import time -import logging -logging.disable(logging.INFO) - -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() From 841b8bf3b51c1f8027368b5b4caef8d4c744ffa2 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 5 Sep 2022 12:25:18 +0800 Subject: [PATCH 32/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8dbcc25..200290c 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ ## 帮助文档 - 查看 [常见问题](docs/FAQ.md) +- 查看 [常用示例](examples) +- 查看 [NtChatHttp接口示例](fastapi_example) - 加入群聊 [PyXCGUI&NtChat交流群](https://jq.qq.com/?_wv=1027&k=oIXzbTbI) - 查看 [PyXCGUI项目](https://github.com/smallevilbeast/pyxcgui) From 138026a1e4b4b35182da1b6332095c1d32401b6a Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 5 Sep 2022 13:15:05 +0800 Subject: [PATCH 33/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0faq.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/FAQ.md b/docs/FAQ.md index f242d48..df48e2f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -4,6 +4,24 @@ 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" +``` ## 如何多开 From 254123f6403c00222349a17e50a67e848cdcc1c5 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 5 Sep 2022 16:39:18 +0800 Subject: [PATCH 34/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=85=AC=E4=BC=97=E5=8F=B7=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/get_publics.py | 35 +++++++++++++++++++++++++++++++++++ ntchat/conf/__init__.py | 2 +- ntchat/const/send_type.py | 3 +++ ntchat/core/wechat.py | 6 ++++++ setup.py | 2 +- 6 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 examples/get_publics.py diff --git a/README.md b/README.md index 200290c..9a55e21 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    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/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 685af60..09496f8 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.7' +VERSION = '0.1.8' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/const/send_type.py b/ntchat/const/send_type.py index 05b8c5a..f5cada4 100644 --- a/ntchat/const/send_type.py +++ b/ntchat/const/send_type.py @@ -7,6 +7,9 @@ # 获取所有的群 MT_GET_ROOMS_MSG = 11031 +# 获取公众号列表 +MT_GET_PUBLICS_MSG = 11033 + # 获取指定的群成员 MT_GET_ROOM_MEMBERS_MSG = 11032 diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index ec0cdee..722514b 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -181,6 +181,12 @@ def get_contacts(self): """ 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): """ 获取联系人详细信息 diff --git a/setup.py b/setup.py index 872cdfd..85cb245 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.7', + version='0.1.8', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 91d125722091bac59231cb4fdfa855d07e2c9407 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 6 Sep 2022 11:31:16 +0800 Subject: [PATCH 35/62] =?UTF-8?q?search=5Fcontacts=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A8=A1=E7=B3=8A=E6=90=9C=E7=B4=A2=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ntchat/core/wechat.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 722514b..1ab8b67 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -200,7 +200,8 @@ def search_contacts(self, wxid: Union[None, str] = None, account: Union[None, str] = None, nickname: Union[None, str] = None, - remark: Union[None, str] = None): + remark: Union[None, str] = None, + fuzzy_search: bool = False): """ 根据wxid、微信号、昵称和备注模糊搜索联系人 """ @@ -217,8 +218,9 @@ def search_contacts(self, return [] cond_pairs = [] + tag = '%' if fuzzy_search else '' for k, v in conds.items(): - cond_pairs.append(f"{k} like '%{v}%'") + cond_pairs.append(f"{k} like '{tag}{v}{tag}'") cond_str = " or ".join(cond_pairs) sql = f"select username from contact where {cond_str}" From 008e6867eff49b137618c0c1c3b0884c3b18afcf Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 6 Sep 2022 13:17:07 +0800 Subject: [PATCH 36/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E8=BD=B0=E7=82=B8=E6=9C=BA=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/bomber.py | 43 +++++++++++++++++++++++++++++++++++++++++ ntchat/conf/__init__.py | 2 +- ntchat/core/wechat.py | 3 +-- setup.py | 2 +- 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 examples/bomber.py diff --git a/README.md b/README.md index 9a55e21..e85bc81 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    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/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 09496f8..51a1dd2 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.8' +VERSION = '0.1.10' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 1ab8b67..9b13e11 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -201,7 +201,7 @@ def search_contacts(self, account: Union[None, str] = None, nickname: Union[None, str] = None, remark: Union[None, str] = None, - fuzzy_search: bool = False): + fuzzy_search: bool = True): """ 根据wxid、微信号、昵称和备注模糊搜索联系人 """ @@ -225,7 +225,6 @@ def search_contacts(self, cond_str = " or ".join(cond_pairs) sql = f"select username from contact where {cond_str}" message = self.sql_query(sql, 1) - print(message) if not message: return [] diff --git a/setup.py b/setup.py index 85cb245..2bd55f6 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.8', + version='0.1.10', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 80bc605bfa2f2c0d292354066c465b0d130cfca4 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 6 Sep 2022 19:46:57 +0800 Subject: [PATCH 37/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=9F=A5=E8=AF=A2=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/sql_query.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 examples/sql_query.py diff --git a/examples/sql_query.py b/examples/sql_query.py new file mode 100644 index 0000000..3998861 --- /dev/null +++ b/examples/sql_query.py @@ -0,0 +1,34 @@ +# -*- 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() From da213b01aff3a09bdf7b6e0b01eac67d724bb5ca Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 7 Sep 2022 09:54:37 +0800 Subject: [PATCH 38/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e85bc81..4aa377e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - 支持好友和群管理 ## 支持的微信版本下载 -- [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe) +- 官方下载 [WeChatSetup3.6.0.18.exe](https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe) ## 帮助文档 - 查看 [常见问题](docs/FAQ.md) From b09ce239974fc75ee2505e2bf575dc6f65b2757d Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 7 Sep 2022 12:51:27 +0800 Subject: [PATCH 39/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0fastapi=5Fexample?= =?UTF-8?q?=E6=89=93=E5=8C=85=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/FAQ.md b/docs/FAQ.md index df48e2f..0b76682 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -90,3 +90,8 @@ pyinstaller -F --collect-data=ntchat main.py ```bash pyinstaller -y --collect-data=ntchat main.py ``` + +打包fastapi_example示例,需要添加`--paths=. --collect-data=ntchat` +```bash +pyinstaller -F --paths=. --collect-data=ntchat main.py +``` \ No newline at end of file From e279b6e6fc7b9a96653e186b3cc15bfcfb46827d Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 7 Sep 2022 17:58:15 +0800 Subject: [PATCH 40/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dfastapi=5Fexample?= =?UTF-8?q?=E7=9A=84get=5Fcontacts=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index 939e384..b6d25e6 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -82,7 +82,6 @@ async def user_get_profile(model: models.ClientReqModel): @catch_exception() async def get_contacts(model: models.ClientReqModel): data = client_mgr.get_client(model.guid).get_contacts() - print(data) return response_json(1, data) From 6e30f1f24384e618f9dad6a738c004975d6c3293 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 8 Sep 2022 10:57:19 +0800 Subject: [PATCH 41/62] =?UTF-8?q?=E6=96=B0=E5=A2=9EImportError=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 0b76682..8eb0ca4 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,4 +1,4 @@ -## WeChatVersionNotMatchError异常 +## 1. WeChatVersionNotMatchError异常 如果出现`ntchat.exception.WeChatVersionNotMatchError`异常, 请确认是否安装github上指定的微信版本,如果确认已经安装,还是报错,可以在代码中添加以下代码,跳过微信版本检测 ```python import ntchat @@ -23,8 +23,13 @@ Windows Registry Editor Version 5.00 "InstallPath"="C:\Program Files (x86)\Tencent\WeChat" ``` +## 2. `ImportError: cannot import name 'wcprobe' from ''ntwork.wc'` -## 如何多开 +出现在这个错误的原因是因为你在github下载的源码目录中运行程序,因为wcprobe是根据python版本自动编译生成的,所以源码目录中没有这个文件. + +你需要将要`运行的例子文件移动到非源码目录下`,再去运行或打包 + +## 3. 如何多开 新建多个ntchat.WeChat实例,然后调用open方法: ```python @@ -37,7 +42,7 @@ for i in range(3): ``` 更完善的多实例管理查看[fastapi_example例子](./fastapi_example) -## 如何监听输出所有的消息 +## 4. 如何监听输出所有的消息 ```python # 注册监听所有消息回调 @wechat.msg_register(ntchat.MT_ALL) @@ -47,7 +52,7 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message): ``` 完全例子查看[examples/msg_register_all.py](../examples/msg_register_all.py) -## 如何关闭NtChat的日志 +## 5. 如何关闭NtChat的日志 `os.environ['NTCHAT_LOG'] = "ERROR"` 要在`import ntchat`前执行 ```python @@ -60,7 +65,7 @@ os.environ['NTCHAT_LOG'] = "ERROR" import ntchat ``` -## 如何正常的关闭Cmd窗口 +## 6. 如何正常的关闭Cmd窗口 先使用`pip install pywin32` 安装pywin32模块, 然后在代码中添加以下代码, 完整例子查看[examples/cmd_close_event.py](../examples/cmd_close_event.py) ```python @@ -78,7 +83,7 @@ win32api.SetConsoleCtrlHandler(on_exit, True) ``` -## pyinstaller打包exe +## 7. pyinstaller打包exe 使用pyinstaller打包NtChat项目,需要添加`--collect-data=ntchat`选项 打包成单个exe程序 From eac6a6484ab9825bc0c65c8a55e962e73d251ab7 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 8 Sep 2022 10:58:45 +0800 Subject: [PATCH 42/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0faq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 8eb0ca4..3e59dc8 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -23,7 +23,7 @@ Windows Registry Editor Version 5.00 "InstallPath"="C:\Program Files (x86)\Tencent\WeChat" ``` -## 2. `ImportError: cannot import name 'wcprobe' from ''ntwork.wc'` +## 2. `ImportError: cannot import name 'wcprobe' from ''ntchat.wc'` 出现在这个错误的原因是因为你在github下载的源码目录中运行程序,因为wcprobe是根据python版本自动编译生成的,所以源码目录中没有这个文件. From 38d34ffb9733bdbff46b9435e21d4743151e66cc Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 8 Sep 2022 11:00:15 +0800 Subject: [PATCH 43/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0faq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 3e59dc8..bde7be8 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -23,7 +23,7 @@ Windows Registry Editor Version 5.00 "InstallPath"="C:\Program Files (x86)\Tencent\WeChat" ``` -## 2. `ImportError: cannot import name 'wcprobe' from ''ntchat.wc'` +## 2. `ImportError: cannot import name 'wcprobe' from 'ntchat.wc'` 出现在这个错误的原因是因为你在github下载的源码目录中运行程序,因为wcprobe是根据python版本自动编译生成的,所以源码目录中没有这个文件. From 8db8998aaaf80c9fb09bd5ef21e9cd8d61a5607f Mon Sep 17 00:00:00 2001 From: evilbeast Date: Thu, 8 Sep 2022 12:30:39 +0800 Subject: [PATCH 44/62] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8B=8D=E4=B8=80?= =?UTF-8?q?=E6=8B=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- ntchat/__init__.py | 7 ++++++- ntchat/conf/__init__.py | 2 +- ntchat/const/send_type.py | 3 +++ ntchat/core/wechat.py | 10 ++++++++++ setup.py | 2 +- 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4aa377e..9ba8922 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    diff --git a/ntchat/__init__.py b/ntchat/__init__.py index b641727..d24e420 100644 --- a/ntchat/__init__.py +++ b/ntchat/__init__.py @@ -16,4 +16,9 @@ def set_wechat_exe_path(wechat_exe_path=None, wechat_version=None): conf.DEFAULT_WECHAT_VERSION = wechat_version -exit_ = wcprobe.exit +def get_install_wechat_version(): + return wcprobe.get_install_wechat_version() + + +def exit_(): + wcprobe.exit() diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 51a1dd2..8a982cd 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.10' +VERSION = '0.1.11' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/const/send_type.py b/ntchat/const/send_type.py index f5cada4..a89dbeb 100644 --- a/ntchat/const/send_type.py +++ b/ntchat/const/send_type.py @@ -75,3 +75,6 @@ # 数据库查询 MT_SQL_QUERY_MSG = 11027 + +# 拍一拍 +MT_SEND_PAT_MSG = 11250 diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 9b13e11..dc0da41 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -360,6 +360,16 @@ def send_xml(self, to_wxid, xml, app_type=5): } 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): """ 同意加好友请求 diff --git a/setup.py b/setup.py index 2bd55f6..590eb05 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.10', + version='0.1.11', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 9a0cc00493f37cfebaa66394037ab8245a147776 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Fri, 9 Sep 2022 13:42:09 +0800 Subject: [PATCH 45/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9C=A8=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E4=B8=AD=E8=B0=83=E7=94=A8=E5=90=8C=E6=AD=A5=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=BF=94=E5=9B=9ENone=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/auto_accept_friend_request.py | 6 +++++- ntchat/conf/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ba8922..59e5a1c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    diff --git a/examples/auto_accept_friend_request.py b/examples/auto_accept_friend_request.py index 6af0f36..676883f 100644 --- a/examples/auto_accept_friend_request.py +++ b/examples/auto_accept_friend_request.py @@ -22,7 +22,11 @@ def on_recv_text_msg(wechat_instance: ntchat.WeChat, message): scene = dom.documentElement.getAttribute("scene") # 自动同意好友申请 - wechat_instance.accept_friend_request(encryptusername, ticket, int(scene)) + ret = wechat_instance.accept_friend_request(encryptusername, ticket, int(scene)) + + if ret: + # 通过后向他发条消息 + wechat_instance.send_text(to_wxid=ret["userName"], content="你好!!!!!") # 以下是为了让程序不结束,如果有用于PyQt等有主循环消息的框架,可以去除下面代码 diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 8a982cd..409e605 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.11' +VERSION = '0.1.12' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/setup.py b/setup.py index 590eb05..ac7e06f 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.11', + version='0.1.12', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 7a7e927442d9f54b76d18694a583fd9fda711ebb Mon Sep 17 00:00:00 2001 From: evilbeast Date: Fri, 9 Sep 2022 15:22:57 +0800 Subject: [PATCH 46/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/main.py | 18 ++++++++++++++++-- fastapi_example/models.py | 10 ++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index b6d25e6..23d09cf 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -3,7 +3,6 @@ from functools import wraps from fastapi import FastAPI from mgr import ClientManager -from typing import List from down import get_local_path from exception import MediaNotExistsError, ClientNotExists import models @@ -243,5 +242,20 @@ async def send_gif(model: models.SendMediaReqModel): return response_json(1 if ret else 0) +@app.post("/msg/send_xml", summary="发送XML原始消息", tags=["Msg"], response_model=models.ResponseModel) +@catch_exception() +async def send_gif(model: models.SendXmlReqModel): + ret = client_mgr.get_client(model.guid).send_xml(model.to_wxid, model.xml) + return response_json(1 if ret else 0) + + +@app.post("/msg/send_pat", summary="发送拍一拍", tags=["Msg"], response_model=models.ResponseModel) +@catch_exception() +async def send_gif(model: models.SendPatReqModel): + data = client_mgr.get_client(model.guid).send_pat(model.room_wxid, model.patted_wxid) + return response_json(1, data) + + + if __name__ == '__main__': - uvicorn.run(app=app) + uvicorn.run(app=app, host='0.0.0.0', port=8000) diff --git a/fastapi_example/models.py b/fastapi_example/models.py index 7db4304..3a248c5 100644 --- a/fastapi_example/models.py +++ b/fastapi_example/models.py @@ -135,3 +135,13 @@ class SendLinkCardReqModel(SendMsgReqModel): class SendMediaReqModel(SendMsgReqModel): file_path: Optional[str] = "" url: Optional[str] = "" + + +class SendXmlReqModel(SendMsgReqModel): + xml: str + + +class SendPatReqModel(ClientReqModel): + room_wxid: str + patted_wxid: str + From d91c807905626c3a1e82976c3590ec31871f61a8 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Fri, 9 Sep 2022 15:27:01 +0800 Subject: [PATCH 47/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- fastapi_example/main.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 59e5a1c..b2016ee 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - 支持好友和群管理 ## 支持的微信版本下载 -- 官方下载 [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) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index 23d09cf..8b3c5b2 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -256,6 +256,5 @@ async def send_gif(model: models.SendPatReqModel): return response_json(1, data) - if __name__ == '__main__': uvicorn.run(app=app, host='0.0.0.0', port=8000) From cf69a36a72053453c12909e97af4d05c4a6018f9 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sat, 10 Sep 2022 12:16:54 +0800 Subject: [PATCH 48/62] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8F=91=E9=80=81?= =?UTF-8?q?=E7=BE=A4@=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/send_room_at_msg.py | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/send_room_at_msg.py 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() + + + + From 5ff92ea0c7a2b0fd5b9acc6b91fb4b63c9d74648 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 13 Sep 2022 21:39:19 +0800 Subject: [PATCH 49/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dpython3.6=E8=BF=90?= =?UTF-8?q?=E8=A1=8Cbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/down.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fastapi_example/down.py b/fastapi_example/down.py index 8fb9699..b00582d 100644 --- a/fastapi_example/down.py +++ b/fastapi_example/down.py @@ -1,8 +1,15 @@ import os.path -import time import requests from xdg import get_download_dir from models import SendMediaReqModel +from ntchat.utils import generate_guid + + +def new_download_file(): + while True: + path = os.path.join(get_download_dir(), generate_guid("temp")) + if not os.path.isfile(path): + return path def get_local_path(model: SendMediaReqModel): @@ -11,7 +18,7 @@ def get_local_path(model: SendMediaReqModel): if not model.url: return None data = requests.get(model.url).content - temp_file = os.path.join(get_download_dir(), str(time.time_ns())) + temp_file = new_download_file() with open(temp_file, 'wb') as fp: fp.write(data) return temp_file From 15b408d390407a223fef75b22d076e2e3327aa93 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 13 Sep 2022 21:43:06 +0800 Subject: [PATCH 50/62] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=BB=9D=E5=AF=B9?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../f4c92056-f401-3984-b754-1b2cd1f995b6 | Bin 0 -> 55221 bytes fastapi_example/xdg.py | 1 + 2 files changed, 1 insertion(+) create mode 100644 fastapi_example/download/f4c92056-f401-3984-b754-1b2cd1f995b6 diff --git a/fastapi_example/download/f4c92056-f401-3984-b754-1b2cd1f995b6 b/fastapi_example/download/f4c92056-f401-3984-b754-1b2cd1f995b6 new file mode 100644 index 0000000000000000000000000000000000000000..d2da0fd77ab4f4fbd12b89863f9bc039b5714eb2 GIT binary patch literal 55221 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

    ~DSIP*x}g(=pTEEU<$7{rooH?lm9 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 literal 0 HcmV?d00001 diff --git a/fastapi_example/xdg.py b/fastapi_example/xdg.py index f4c6331..3243b8d 100644 --- a/fastapi_example/xdg.py +++ b/fastapi_example/xdg.py @@ -9,6 +9,7 @@ def get_exec_dir(): def get_download_dir(): user_dir = os.path.join(get_exec_dir(), 'download') + user_dir = os.path.abspath(user_dir) if not os.path.isdir(user_dir): os.makedirs(user_dir) return user_dir From 3f228403343d0b5770f12eeb67bb53d9e3da298a Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 13 Sep 2022 21:44:03 +0800 Subject: [PATCH 51/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0fastapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../f4c92056-f401-3984-b754-1b2cd1f995b6 | Bin 55221 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 fastapi_example/download/f4c92056-f401-3984-b754-1b2cd1f995b6 diff --git a/fastapi_example/download/f4c92056-f401-3984-b754-1b2cd1f995b6 b/fastapi_example/download/f4c92056-f401-3984-b754-1b2cd1f995b6 deleted file mode 100644 index d2da0fd77ab4f4fbd12b89863f9bc039b5714eb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55221 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

    ~DSIP*x}g(=pTEEU<$7{rooH?lm9 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 From f7ba8fdbbfd11219ec8060be0647956938bd804e Mon Sep 17 00:00:00 2001 From: evilbeast Date: Mon, 19 Sep 2022 17:05:42 +0800 Subject: [PATCH 52/62] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E5=87=BD=E6=95=B0=E6=9C=89=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=97=B6=E7=A8=8B=E5=BA=8F=E4=BC=9A=E9=80=80=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- README.md | 2 +- ntchat/conf/__init__.py | 2 +- ntchat/const/send_type.py | 3 +++ ntchat/core/wechat.py | 25 +++++++++++++++++++++++-- setup.py | 2 +- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index cde6f02..05bea95 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ ntchat/wc/*.pyd ntchat/wc/*.dat wheelhouse/ setup_conf.py -upload.bat \ No newline at end of file +upload.bat +download/ \ No newline at end of file diff --git a/README.md b/README.md index b2016ee..863c0f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 409e605..4164d4b 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.12' +VERSION = '0.1.13' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/const/send_type.py b/ntchat/const/send_type.py index a89dbeb..5fc5793 100644 --- a/ntchat/const/send_type.py +++ b/ntchat/const/send_type.py @@ -46,6 +46,9 @@ # 发送xml消息 MT_SEND_XML_MSG = 11113 +# 修改好友备注 +MT_MODIFY_FRIEND_REMARK = 11063 + # 接受新好友请求 MT_ACCEPT_FRIEND_MSG = 11065 diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index dc0da41..8e43efa 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -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 @@ -55,7 +66,7 @@ def __init__(self): self.__login_info = {} def on(self, msg_type, f): - return self.__msg_event_emitter.on(str(msg_type), f) + return self.__msg_event_emitter.on(str(msg_type), RaiseExceptionFunc(f)) def msg_register(self, msg_type: Union[int, List[int], Tuple[int]]): if not (isinstance(msg_type, list) or isinstance(msg_type, tuple)): @@ -64,7 +75,7 @@ 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(event, RaiseExceptionFunc(f)) return f return wrapper @@ -457,3 +468,13 @@ def quit_room(self, room_wxid: str): "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) diff --git a/setup.py b/setup.py index ac7e06f..3cdb8ee 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.12', + version='0.1.13', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 476e8428cb87093cd8d3f0f20978f17d0457a21e Mon Sep 17 00:00:00 2001 From: evilbeast Date: Tue, 20 Sep 2022 09:29:09 +0800 Subject: [PATCH 53/62] =?UTF-8?q?fastapi=E6=B7=BB=E5=8A=A0=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=A4=87=E6=B3=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/main.py | 9 +++++++++ fastapi_example/models.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index 8b3c5b2..b9d1987 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -92,6 +92,13 @@ async def get_contact_detail(model: models.ContactDetailReqModel): return response_json(1, data) +@app.post("/contact/modify_remark", summary="修改联系人备注", tags=["Contact"], response_model=models.ResponseModel) +@catch_exception() +async def send_gif(model: models.ModifyFriendRemarkReqModel): + data = client_mgr.get_client(model.guid).modify_friend_remark(model.wxid, model.remark) + return response_json(1, data) + + @app.post("/room/get_rooms", summary="获取群列表", tags=["Room"], response_model=models.ResponseModel) @catch_exception() @@ -256,5 +263,7 @@ async def send_gif(model: models.SendPatReqModel): return response_json(1, data) + + if __name__ == '__main__': uvicorn.run(app=app, host='0.0.0.0', port=8000) diff --git a/fastapi_example/models.py b/fastapi_example/models.py index 3a248c5..e1f1e30 100644 --- a/fastapi_example/models.py +++ b/fastapi_example/models.py @@ -145,3 +145,9 @@ class SendPatReqModel(ClientReqModel): room_wxid: str patted_wxid: str + +class ModifyFriendRemarkReqModel(ClientReqModel): + wxid: str + remark: str + + From 99ac6bf0078c14be4cff21e21871509e70411216 Mon Sep 17 00:00:00 2001 From: daobing zhu Date: Fri, 23 Sep 2022 08:42:30 +0800 Subject: [PATCH 54/62] add transmit example --- examples/transmit.py | 117 ++++++++++++++++++++++++++++++++++++ ntchat/const/notify_type.py | 3 + 2 files changed, 120 insertions(+) create mode 100644 examples/transmit.py diff --git a/examples/transmit.py b/examples/transmit.py new file mode 100644 index 0000000..cbba65f --- /dev/null +++ b/examples/transmit.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +from email import message +from email.mime import image +import sys +import os.path +import time +import ntchat +import xml.etree.ElementTree as ET +import re + +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(ntchat.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/ntchat/const/notify_type.py b/ntchat/const/notify_type.py index e1d017f..60e748c 100644 --- a/ntchat/const/notify_type.py +++ b/ntchat/const/notify_type.py @@ -23,6 +23,9 @@ MT_RECV_IMAGE_MSG = 11047 MT_RECV_PICTURE_MSG = 11047 +# 聊天记录通知 +MT_RECV_CHAT_RECORDS_MSG = 11061 + # 语音消息通知 MT_RECV_VOICE_MSG = 11048 From cd51f463bc50ccd1287ec73275208a1251cd15ba Mon Sep 17 00:00:00 2001 From: daobing zhu Date: Fri, 23 Sep 2022 08:43:35 +0800 Subject: [PATCH 55/62] remove unuse import --- examples/transmit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/transmit.py b/examples/transmit.py index cbba65f..cb5f347 100644 --- a/examples/transmit.py +++ b/examples/transmit.py @@ -5,7 +5,6 @@ import os.path import time import ntchat -import xml.etree.ElementTree as ET import re wechat = ntchat.WeChat() From cdaacf186b7aa316e9640abc91636061daac91a4 Mon Sep 17 00:00:00 2001 From: daobing zhu Date: Fri, 23 Sep 2022 17:23:25 +0800 Subject: [PATCH 56/62] address comment --- examples/transmit.py | 5 ++++- ntchat/const/notify_type.py | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/transmit.py b/examples/transmit.py index cb5f347..9062f84 100644 --- a/examples/transmit.py +++ b/examples/transmit.py @@ -7,6 +7,9 @@ import ntchat import re +# 聊天记录通知 +MT_RECV_CHAT_RECORDS_MSG = 11061 + wechat = ntchat.WeChat() # 要监听的wxids,可以通过获取contact接口获取wxid,也可以开启后从debug信息中看出来 @@ -81,7 +84,7 @@ def update_wxid_in_xml(xml, from_wxid, target_wxid): return patten.sub(target_wxid, xml) -@wechat.msg_register(ntchat.MT_RECV_CHAT_RECORDS_MSG) +@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"] diff --git a/ntchat/const/notify_type.py b/ntchat/const/notify_type.py index 60e748c..e1d017f 100644 --- a/ntchat/const/notify_type.py +++ b/ntchat/const/notify_type.py @@ -23,9 +23,6 @@ MT_RECV_IMAGE_MSG = 11047 MT_RECV_PICTURE_MSG = 11047 -# 聊天记录通知 -MT_RECV_CHAT_RECORDS_MSG = 11061 - # 语音消息通知 MT_RECV_VOICE_MSG = 11048 From 734eab020e059aec9bfb4e33a16b60a58812171e Mon Sep 17 00:00:00 2001 From: evilbeast Date: Fri, 23 Sep 2022 18:19:54 +0800 Subject: [PATCH 57/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0send=5Ftext=5Fui?= =?UTF-8?q?=E4=BE=8B=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 ++++- examples/send_text_ui.py | 3 ++- fastapi_example/main.py | 2 -- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 863c0f1..4ca0689 100644 --- a/README.md +++ b/README.md @@ -146,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): @@ -171,6 +171,8 @@ class NtChatWindow(XWindow): def on_btn_open_clicked(self, sender, _): self.wechat_instance = ntchat.WeChat() self.wechat_instance.open(smart=True) + + # 监听所有通知消息 self.wechat_instance.on(ntchat.MT_ALL, self.on_recv_message) def on_btn_send_clicked(self, sender, _): @@ -182,6 +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/examples/send_text_ui.py b/examples/send_text_ui.py index daa3842..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): @@ -38,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/fastapi_example/main.py b/fastapi_example/main.py index b9d1987..9c4de2b 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -263,7 +263,5 @@ async def send_gif(model: models.SendPatReqModel): return response_json(1, data) - - if __name__ == '__main__': uvicorn.run(app=app, host='0.0.0.0', port=8000) From ae450372b1581ad67fb87252a37febd5fc4592b7 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 28 Sep 2022 10:56:35 +0800 Subject: [PATCH 58/62] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- examples/show_login_qrcode.py | 18 ++++++++++++++++++ fastapi_example/main.py | 2 +- ntchat/conf/__init__.py | 2 +- ntchat/core/wechat.py | 5 ++++- setup.py | 2 +- 6 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 examples/show_login_qrcode.py diff --git a/README.md b/README.md index 4ca0689..e06eacd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    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/fastapi_example/main.py b/fastapi_example/main.py index 9c4de2b..bc82691 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -56,7 +56,7 @@ async def client_create(): response_model=models.ResponseModel) @catch_exception() async def client_open(model: models.ClientOpenReqModel): - ret = client_mgr.get_client(model.guid).open(model.smart) + ret = client_mgr.get_client(model.guid).open(model.smart, model.show_login_qrcode) return response_json(1 if ret else 0) diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index 4164d4b..fc256b7 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.13' +VERSION = '0.1.15' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 8e43efa..1582b0f 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -120,7 +120,10 @@ 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 diff --git a/setup.py b/setup.py index 3cdb8ee..9547166 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.13', + version='0.1.15', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From f7fb72725708d3522dd72f05a43c95803463af99 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Wed, 28 Sep 2022 15:48:32 +0800 Subject: [PATCH 59/62] =?UTF-8?q?fastapi=E4=BE=8B=E5=AD=90open=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=BF=94=E5=9B=9E=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi_example/main.py | 11 +++++++++-- fastapi_example/mgr.py | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/fastapi_example/main.py b/fastapi_example/main.py index bc82691..54e7697 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import uvicorn +import threading from functools import wraps from fastapi import FastAPI from mgr import ClientManager @@ -56,8 +57,14 @@ async def client_create(): response_model=models.ResponseModel) @catch_exception() async def client_open(model: models.ClientOpenReqModel): - ret = client_mgr.get_client(model.guid).open(model.smart, model.show_login_qrcode) - return response_json(1 if ret else 0) + client = client_mgr.get_client(model.guid) + ret = client.open(model.smart, model.show_login_qrcode) + + # 当show_login_qrcode=True时, 打开微信时会显示二维码界面 + if model.show_login_qrcode: + client.qrcode_event = threading.Event() + client.qrcode_event.wait(timeout=10) + return response_json(1 if ret else 0, {'qrcode': client.qrcode}) @app.post("/global/set_callback_url", summary="设置接收通知地址", tags=["Global"], diff --git a/fastapi_example/mgr.py b/fastapi_example/mgr.py index 7be9b6b..61f8e04 100644 --- a/fastapi_example/mgr.py +++ b/fastapi_example/mgr.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import ntchat +import threading import requests from typing import Dict, Union from ntchat.utils.singleton import Singleton @@ -9,6 +10,8 @@ class ClientWeChat(ntchat.WeChat): guid: str = "" + qrcode_event: threading.Event = None + qrcode: str = "" class ClientManager(metaclass=Singleton): @@ -35,7 +38,7 @@ def create_client(self): wechat.on(ntchat.MT_RECV_WECHAT_QUIT_MSG, self.__on_quit_callback) return guid - def get_client(self, guid: str) -> Union[None, ntchat.WeChat]: + def get_client(self, guid: str) -> ClientWeChat: client = self.__client_map.get(guid, None) if client is None: raise ClientNotExists(guid) @@ -45,7 +48,14 @@ def remove_client(self, guid): if guid in self.__client_map: del self.__client_map[guid] - def __on_callback(self, wechat, message): + def __on_callback(self, wechat: ClientWeChat, message: dict): + + # 通知二维码显示 + msg_type = message['type'] + if msg_type == ntchat.MT_RECV_LOGIN_QRCODE_MSG and wechat.qrcode_event: + wechat.qrcode = message["data"]["code"] + wechat.qrcode_event.set() + if not self.callback_url: return From 38506a0e7bcb1f8da810e50a3994ffe6f89f17df Mon Sep 17 00:00:00 2001 From: evilbeast Date: Fri, 7 Oct 2022 11:34:28 +0800 Subject: [PATCH 60/62] =?UTF-8?q?=E4=BC=98=E5=8C=96msg=5Fregister?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ntchat/core/wechat.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 1582b0f..5c618d0 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -62,34 +62,32 @@ 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), RaiseExceptionFunc(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, RaiseExceptionFunc(f)) + self.on(msg_type, f) return f - return wrapper def on_close(self): self.login_status = False self.status = False - self.__msg_event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self) + self.event_emitter.emit(str(notify_type.MT_RECV_WECHAT_QUIT_MSG), self) message = { "type": notify_type.MT_RECV_WECHAT_QUIT_MSG, "data": {} } - self.__msg_event_emitter.emit(str(notify_type.MT_ALL), self, message) + self.event_emitter.emit(str(notify_type.MT_ALL), self, message) def bind_client_id(self, client_id): self.status = True @@ -113,8 +111,8 @@ 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(notify_type.MT_ALL), self, message) + self.event_emitter.emit(str(msg_type), self, message) + self.event_emitter.emit(str(notify_type.MT_ALL), self, message) def wait_login(self, timeout=None): log.info("wait login...") From 330f0724f3062dfa2ff1d96b55aefe8607f6462f Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sun, 9 Oct 2022 12:37:03 +0800 Subject: [PATCH 61/62] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- fastapi_example/main.py | 11 +++++++++++ fastapi_example/models.py | 4 ++++ ntchat/conf/__init__.py | 2 +- ntchat/core/mgr.py | 15 +++++++++++---- ntchat/core/wechat.py | 10 ++++++++++ ntchat/exception/__init__.py | 4 ++++ ntchat/utils/xdg.py | 13 +++++++++---- ntchat/wc/__init__.py | 3 +++ setup.py | 2 +- 10 files changed, 55 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e06eacd..3d7f449 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

    NtChat

    - release + release License

    diff --git a/fastapi_example/main.py b/fastapi_example/main.py index 54e7697..5bcff80 100644 --- a/fastapi_example/main.py +++ b/fastapi_example/main.py @@ -114,6 +114,17 @@ async def get_rooms(model: models.ClientReqModel): return response_json(1, data) +@app.post("/room/get_name_name", summary="获取群名称", tags=["Room"], + response_model=models.ResponseModel) +@catch_exception() +async def get_rooms(model: models.GetRoomNameReqModel): + name = client_mgr.get_client(model.guid).get_room_name(model.room_wxid) + data = { + "name": name + } + return response_json(1, data) + + @app.post("/room/get_room_members", summary="获取群成员列表", tags=["Room"], response_model=models.ResponseModel) @catch_exception() diff --git a/fastapi_example/models.py b/fastapi_example/models.py index e1f1e30..1028c51 100644 --- a/fastapi_example/models.py +++ b/fastapi_example/models.py @@ -87,6 +87,10 @@ class GetRoomMembersReqModel(ClientReqModel): room_wxid: str +class GetRoomNameReqModel(ClientReqModel): + room_wxid: str + + class CreateRoomReqModel(ClientReqModel): member_list: List[str] diff --git a/ntchat/conf/__init__.py b/ntchat/conf/__init__.py index fc256b7..1e3ba15 100644 --- a/ntchat/conf/__init__.py +++ b/ntchat/conf/__init__.py @@ -1,4 +1,4 @@ -VERSION = '0.1.15' +VERSION = '0.1.16' LOG_LEVEL = "DEBUG" LOG_KEY = 'NTCHAT_LOG' diff --git a/ntchat/core/mgr.py b/ntchat/core/mgr.py index 4a7a943..baacf1e 100644 --- a/ntchat/core/mgr.py +++ b/ntchat/core/mgr.py @@ -1,8 +1,8 @@ import json import os.path -from ntchat.wc import wcprobe -from ntchat.utils.xdg import get_helper_file -from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError +from ntchat.wc import wcprobe, SUPPORT_VERSIONS +from ntchat.utils.xdg import get_helper_file, is_support_version, has_helper_file +from ntchat.exception import WeChatVersionNotMatchError, WeChatBindError, WeChatRuntimeError from ntchat.utils.singleton import Singleton from ntchat.const import notify_type from ntchat.utils.logger import get_logger @@ -31,9 +31,16 @@ 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) diff --git a/ntchat/core/wechat.py b/ntchat/core/wechat.py index 5c618d0..49df2c2 100644 --- a/ntchat/core/wechat.py +++ b/ntchat/core/wechat.py @@ -479,3 +479,13 @@ def modify_friend_remark(self, wxid: str, remark: str): "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/xdg.py b/ntchat/utils/xdg.py index 7825678..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(): @@ -26,9 +27,13 @@ def get_helper_file(version): return os.path.join(get_wc_dir(), f"helper_{version}.dat") -def get_support_download_url(): - return 'https://webcdn.m.qq.com/spcmgr/download/WeChat3.6.0.18.exe' +def has_helper_file(): + for name in os.listdir(get_wc_dir()): + if name.startswith("helper_"): + return True + return False -if __name__ == '__main__': - print(get_helper_file('3.6.0.18')) +def is_support_version(version): + return version in SUPPORT_VERSIONS + 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 9547166..10a6b87 100644 --- a/setup.py +++ b/setup.py @@ -194,7 +194,7 @@ def add_prefix(l, prefix): setup( name='ntchat', - version='0.1.15', + version='0.1.16', description='About Conversational RPA SDK for Chatbot Makers', long_description="", long_description_content_type='text/markdown', From 90c824d0b010e8f99f45ab0771019a167686c334 Mon Sep 17 00:00:00 2001 From: evilbeast Date: Sun, 9 Oct 2022 12:46:55 +0800 Subject: [PATCH 62/62] =?UTF-8?q?=E5=8E=BB=E9=99=A4sql=5Fquery=E4=BE=8B?= =?UTF-8?q?=E5=AD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/sql_query.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 examples/sql_query.py diff --git a/examples/sql_query.py b/examples/sql_query.py deleted file mode 100644 index 3998861..0000000 --- a/examples/sql_query.py +++ /dev/null @@ -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()