diff --git a/robot/configuration.py b/configuration.py similarity index 91% rename from robot/configuration.py rename to configuration.py index 5af1314..400bb5a 100644 --- a/robot/configuration.py +++ b/configuration.py @@ -16,7 +16,7 @@ class Config(object): with open(f"{pwd}/config.yaml", "rb") as fp: yconfig = yaml.safe_load(fp) except FileNotFoundError: - with open(f"{pwd}/../config.yaml.template", "rb") as fp: + with open(f"{pwd}/config.yaml.template", "rb") as fp: yconfig = yaml.safe_load(fp) with open(f"{pwd}/config.yaml", "w+") as yf: yaml.dump(yconfig, yf, default_flow_style=False) diff --git a/robot/job_mgmt.py b/job_mgmt.py similarity index 100% rename from robot/job_mgmt.py rename to job_mgmt.py diff --git a/main.py b/main.py index cfd2627..fcbc203 100644 --- a/main.py +++ b/main.py @@ -1,103 +1,10 @@ #! /usr/bin/env python3 # -*- coding: utf-8 -*- -import re +import signal +from wcferry import Wcf -from func_chengyu import cy -import robot.sdk.wcferry as WxSDK -from robot.base_robot import BaseRobot -from robot.configuration import Config - - -class Robot(BaseRobot): - """个性化自己的机器人 - """ - - def __init__(self, sdk: WxSDK, config: Config) -> None: - super().__init__(sdk) - self.config = config - - def toAt(self, msg) -> bool: - """ - 处理被 @ 消息,现在只固定回复: "你@我干嘛?" - :param msg: 微信消息结构 - :return: 处理状态,`True` 成功,`False` 失败 - """ - status = True - rsp = "你@我干嘛?" - self.sendTextMsg(msg.roomId, rsp, msg.wxId) - - return status - - def toChengyu(self, msg) -> bool: - """ - 处理成语查询/接龙消息 - :param msg: 微信消息结构 - :return: 处理状态,`True` 成功,`False` 失败 - """ - status = False - texts = re.findall(r"^([#|?|?])(.*)$", msg.content) - # [('#', '天天向上')] - if texts: - flag = texts[0][0] - text = texts[0][1] - if flag == "#": # 接龙 - if cy.isChengyu(text): - rsp = cy.getNext(text) - if rsp: - self.sendTextMsg(msg.roomId, rsp) - status = True - elif flag in ["?", "?"]: # 查词 - if cy.isChengyu(text): - rsp = cy.getMeaning(text) - if rsp: - self.sendTextMsg(msg.roomId, rsp) - status = True - - return status - - def toChitchat(self, msg): - """闲聊,目前未实现 - """ - pass - - def processMsg(self, msg) -> None: - """当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。 - """ - - self.printRawMsg(msg) # 打印信息 - - # 群聊消息 - if self.isGroupChat(msg): - # 如果在群里被 @,回复发信人:“收到你的消息了!” 并 @他 - if msg.roomId not in self.config.GROUPS: # 不在配置的响应的群列表里,忽略 - return - - if self.isAt(msg): # 被@ - self.toAt(msg) - - else: # 其他消息 - self.toChengyu(msg) - - # 非群聊信息 - elif msg.type == 37: # 好友请求 - self.autoAcceptFriendRequest(msg) - - elif msg.type == 10000: # 系统信息 - nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content) - if nickName: - # 添加了好友,更新好友列表 - self.allContacts[msg.wxId] = nickName - - elif msg.type == 0x01: # 文本消息 - # 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。 - if msg.self and msg.content == "^更新$": - self.config.reload() - self.LOG.info("已更新") - return - - # 闲聊 - self.toChitchat(msg) +from robot import Robot def weather_report(robot: Robot): @@ -110,14 +17,20 @@ def weather_report(robot: Robot): report = "这就是获取到的天气情况了" for r in receivers: - robot.sendTextMsg(r, report) + robot.sendTextMsg(report, r) def main(): - robot = Robot(WxSDK, Config()) + wcf = Wcf() - # 初始化机器人 - robot.initSDK() + def handler(sig, frame): + wcf.cleanup() # 退出前清理环境 + exit(0) + + signal.signal(signal.SIGINT, handler) + + robot = Robot(wcf) + robot.LOG.info("机器人已启动") # 接收消息 robot.enableRecvMsg() diff --git a/robot.py b/robot.py new file mode 100644 index 0000000..96205b1 --- /dev/null +++ b/robot.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +import re +import time +import logging +import xml.etree.ElementTree as ET + +from wcferry import Wcf + +from configuration import Config +from func_chengyu import cy +from job_mgmt import Job + + +class Robot(Job): + """个性化自己的机器人 + """ + + def __init__(self, wcf: Wcf) -> None: + self.wcf = wcf + self.config = Config() + self.LOG = logging.getLogger("Robot") + self.wxid = self.wcf.get_self_wxid() + self.allContacts = self.getAllContacts() + + def toAt(self, msg: Wcf.WxMsg) -> bool: + """ + 处理被 @ 消息,现在只固定回复: "你@我干嘛?" + :param msg: 微信消息结构 + :return: 处理状态,`True` 成功,`False` 失败 + """ + status = True + rsp = "你@我干嘛?" + self.sendTextMsg(rsp, msg.roomid, msg.sender) + + return status + + def toChengyu(self, msg: Wcf.WxMsg) -> bool: + """ + 处理成语查询/接龙消息 + :param msg: 微信消息结构 + :return: 处理状态,`True` 成功,`False` 失败 + """ + status = False + texts = re.findall(r"^([#|?|?])(.*)$", msg.content) + # [('#', '天天向上')] + if texts: + flag = texts[0][0] + text = texts[0][1] + if flag == "#": # 接龙 + if cy.isChengyu(text): + rsp = cy.getNext(text) + if rsp: + self.sendTextMsg(rsp, msg.roomid) + status = True + elif flag in ["?", "?"]: # 查词 + if cy.isChengyu(text): + rsp = cy.getMeaning(text) + if rsp: + self.sendTextMsg(rsp, msg.roomid) + status = True + + return status + + def toChitchat(self, msg: Wcf.WxMsg): + """闲聊,目前未实现 + """ + pass + + def processMsg(self, msg: Wcf.WxMsg) -> None: + """当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。 + """ + # 群聊消息 + if msg.from_group(): + # 如果在群里被 @,回复发信人:“收到你的消息了!” 并 @他 + if msg.roomid not in self.config.GROUPS: # 不在配置的响应的群列表里,忽略 + return + + if msg.is_at(self.wxid): # 被@ + self.toAt(msg) + + else: # 其他消息 + self.toChengyu(msg) + + # 非群聊信息 + elif msg.type == 37: # 好友请求 + self.autoAcceptFriendRequest(msg) + + elif msg.type == 10000: # 系统信息 + self.sayHiToNewFriend(msg) + + elif msg.type == 0x01: # 文本消息 + # 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。 + if msg.from_self() and msg.content == "^更新$": + self.config.reload() + self.LOG.info("已更新") + return + + # 闲聊 + self.toChitchat(msg) + + def onMsg(self, msg) -> int: + self.LOG.info(msg) # 打印信息 + try: + self.processMsg(msg) + except Exception as e: + self.LOG.error(e) + + return 0 + + def enableRecvMsg(self) -> None: + self.wcf.enable_recv_msg(self.onMsg) + + def sendTextMsg(self, msg, receiver, at_list=""): + # msg 中需要有 @ 名单中一样数量的 @ + ats = "" + if at_list: + wxids = at_list.split(",") + for wxid in wxids: + # 这里偷个懒,直接 @昵称。有必要的话可以通过 MicroMsg.db 里的 ChatRoom 表,解析群昵称 + ats = f" @{self.allContacts.get(wxid, '')}" + + self.LOG.info(f"To {receiver}: {msg}{ats}") + self.wcf.send_text(f"{msg}{ats}", receiver, at_list) + + def getAllContacts(self): + """ + 获取联系人(包括好友、公众号、服务号、群成员……) + 格式: {"wxid": "NickName"} + """ + contacts = self.wcf.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;") + return {contact["UserName"]: contact["NickName"] for contact in contacts} + + def keepRunningAndBlockProcess(self) -> None: + """ + 保持机器人运行,不让进程退出 + """ + while True: + self.runPendingJobs() + time.sleep(1) + + def autoAcceptFriendRequest(self, msg): + try: + xml = ET.fromstring(msg.content) + v3 = xml.attrib["encryptusername"] + v4 = xml.attrib["ticket"] + self.wcf.accept_new_friend(v3, v4) + + except Exception as e: + self.LOG.error(f"同意好友出错:{e}") + + def sayHiToNewFriend(self, msg): + nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content) + if nickName: + # 添加了好友,更新好友列表 + self.allContacts[msg.sender] = nickName[0] + self.sendTextMsg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender) diff --git a/robot/base_robot.py b/robot/base_robot.py deleted file mode 100644 index be29b4c..0000000 --- a/robot/base_robot.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -import time -import logging -import xml.etree.ElementTree as ET - -from robot.job_mgmt import Job - - -class BaseRobot(Job): - """ - 机器人基类。用户需要实现 `processMsg` 方法以个性化处理消息。 - """ - - def __init__(self, sdk) -> None: - self.sdk = sdk - self.LOG = logging.getLogger("Robot") - - def onMsg(self, msg) -> int: - try: - self.processMsg(msg) - except Exception as e: - self.LOG.error(e) - self.printRawMsg(msg) - - return 0 - - def initSDK(self): - if self.sdk.WxInitSDK() != 0: - self.LOG.error("初始化失败") - exit(-1) - self.LOG.info("初始化成功") - self.wxid = self.sdk.WxGetSelfWxid() - self.allContacts = self.getAllContacts() - - def enableRecvMsg(self): - self.sdk.WxEnableRecvMsg(self.onMsg) - - def sendTextMsg(self, receiver, msg, at_list=""): - # msg 中需要有 @ 名单中一样数量的 @ - ats = "" - if at_list: - wxids = at_list.split(",") - for wxid in wxids: - # 这里偷个懒,直接 @昵称。有必要的话可以通过 MicroMsg.db 里的 ChatRoom 表,解析群昵称 - ats = f" @{self.allContacts.get(wxid, '')}" - - self.LOG.info(f"To {receiver}: {msg}{ats}") - self.sdk.WxSendTextMsg(receiver, f"{msg}{ats}", at_list) - - def isGroupChat(self, msg): - return msg.source == 1 - - def isAt(self, msg, exclude_at_all=True): - atall = [] - atuserlist = re.findall(f".*({self.wxid}).*", msg.xml) - if exclude_at_all: - atall = re.findall(f"@所有人", msg.content) # 排除@所有人 - - return (len(atuserlist) > 0) and (len(atall) == 0) - - def printRawMsg(self, msg) -> None: - rmsg = {} - rmsg["id"] = msg.id - rmsg["self"] = msg.self - rmsg["wxId"] = msg.wxId - rmsg["roomId"] = msg.roomId - rmsg["type"] = msg.type - rmsg["source"] = msg.source - rmsg["xml"] = msg.xml - rmsg["content"] = msg.content - - self.LOG.info(rmsg) - - def getAllContacts(self): - """ - 获取联系人(包括好友、公众号、服务号、群成员……) - 格式: {"wxid": "NickName"} - """ - contacts = self.sdk.WxExecDbQuery("MicroMsg.db", "SELECT UserName, NickName FROM Contact;") - return {contact["UserName"]: contact["NickName"] for contact in contacts} - - def autoAcceptFriendRequest(self, msg): - try: - xml = ET.fromstring(msg.content) - v3 = xml.attrib["encryptusername"] - v4 = xml.attrib["ticket"] - self.sdk.WxAcceptNewFriend(v3, v4) - - except Exception as e: - self.LOG.error(f"同意好友出错:{e}") - - def processMsg(self, msg) -> None: - raise NotImplementedError("Method [processMsg] should be implemented.") - - def keepRunningAndBlockProcess(self) -> None: - """ - 保持机器人运行,不让进程退出 - """ - while True: - self.runPendingJobs() - time.sleep(1) diff --git a/robot/sdk/App.exe b/robot/sdk/App.exe deleted file mode 100644 index 81bfb38..0000000 Binary files a/robot/sdk/App.exe and /dev/null differ diff --git a/robot/sdk/App.py b/robot/sdk/App.py deleted file mode 100644 index e2a39aa..0000000 --- a/robot/sdk/App.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- - -import time -import wcferry as sdk - - -def main(): - help(sdk) # 查看SDK支持的方法和属性 - - # 初始化SDK,如果成功,返回0;否则失败 - status = sdk.WxInitSDK() - if status != 0: - print("初始化失败") - exit(-1) - - print("初始化成功") - WxMsgTypes = sdk.WxGetMsgTypes() # 获取消息类型 - print(WxMsgTypes) # 查看消息类型 - - time.sleep(2) - print("打印通讯录......") - contacts = sdk.WxGetContacts() - for k, v in contacts.items(): - print(k, v.wxCode, v.wxName, v.wxCountry, v.wxProvince, v.wxCity, v.wxGender) - - time.sleep(2) - print("发送文本消息......") - sdk.WxSendTextMsg("filehelper", "message from WeChatFerry...") # 往文件传输助手发消息 - # sdk.WxSendTextMsg("xxxx@chatroom", "message from WeChatFerry...") # 往群里发消息(需要改成正确的 ID,下同) - # sdk.WxSendTextMsg("xxxx@chatroom", "message from WeChatFerry... @ ", "wxid_xxxxxxxxxxxx") # 往群里发消息,@某人 - # sdk.WxSendTextMsg("xxxx@chatroom", "message from WeChatFerry... @ ", "notify@all") # 往群里发消息,@所有人 - - time.sleep(2) - print("发送图片消息......") - sdk.WxSendImageMsg("filehelper", "test.jpg") - - dbs = sdk.WxGetDbNames() - for db in dbs: - print(db) - - tables = sdk.WxGetDbTables(dbs[0]) - for t in tables: - print(f"{t.table}\n{t.sql}\n\n") - - # 接收消息。先定义消息处理回调 - def OnTextMsg(msg: sdk.WxMessage): - def getName(id): - contact = contacts.get(id) - if contact is None: - return id - return contact.wxName - - s = "收到" - if msg.self == 1: # 忽略自己发的消息 - s += f"来自自己的消息" - print(f"\n{s}") - return 0 - - msgType = WxMsgTypes.get(msg.type, '未知类型') - nickName = getName(msg.wxId) - if msg.source == 1: - groupName = getName(msg.roomId) - s += f"来自群[{groupName}]的[{nickName}]的{msgType}消息:" - else: - s += f"来自[{nickName}]的{msgType}消息:" - - s += f"\r\n{msg.content}" - if msg.type != 0x01: - s += f"\r\n{msg.xml}" - - print(f"\n{s}") - - return 0 - - print("Message: 接收通知中......") - sdk.WxEnableRecvMsg(OnTextMsg) # 设置回调,接收消息 - - while True: - time.sleep(1) - - -if __name__ == '__main__': - main() diff --git a/robot/sdk/SDK.dll b/robot/sdk/SDK.dll deleted file mode 100644 index ce0362d..0000000 Binary files a/robot/sdk/SDK.dll and /dev/null differ diff --git a/robot/sdk/Spy.dll b/robot/sdk/Spy.dll deleted file mode 100644 index a826a32..0000000 Binary files a/robot/sdk/Spy.dll and /dev/null differ diff --git a/robot/sdk/test.jpg b/robot/sdk/test.jpg deleted file mode 100644 index 14aa7a7..0000000 Binary files a/robot/sdk/test.jpg and /dev/null differ diff --git a/robot/sdk/wcferry.pyd b/robot/sdk/wcferry.pyd deleted file mode 100644 index cd7e4c0..0000000 Binary files a/robot/sdk/wcferry.pyd and /dev/null differ