Use wcferry

This commit is contained in:
Changhua
2022-10-22 17:23:57 +08:00
parent 73e9ddf8c3
commit 2b7a1fe45d
11 changed files with 171 additions and 288 deletions

View File

@@ -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)

113
main.py
View File

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

157
robot.py Normal file
View File

@@ -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)

View File

@@ -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"<atuserlist>.*({self.wxid}).*</atuserlist>", 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)

Binary file not shown.

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.