Use wcferry
This commit is contained in:
@@ -16,7 +16,7 @@ class Config(object):
|
|||||||
with open(f"{pwd}/config.yaml", "rb") as fp:
|
with open(f"{pwd}/config.yaml", "rb") as fp:
|
||||||
yconfig = yaml.safe_load(fp)
|
yconfig = yaml.safe_load(fp)
|
||||||
except FileNotFoundError:
|
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)
|
yconfig = yaml.safe_load(fp)
|
||||||
with open(f"{pwd}/config.yaml", "w+") as yf:
|
with open(f"{pwd}/config.yaml", "w+") as yf:
|
||||||
yaml.dump(yconfig, yf, default_flow_style=False)
|
yaml.dump(yconfig, yf, default_flow_style=False)
|
||||||
113
main.py
113
main.py
@@ -1,103 +1,10 @@
|
|||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import re
|
import signal
|
||||||
|
from wcferry import Wcf
|
||||||
|
|
||||||
from func_chengyu import cy
|
from robot import Robot
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def weather_report(robot: Robot):
|
def weather_report(robot: Robot):
|
||||||
@@ -110,14 +17,20 @@ def weather_report(robot: Robot):
|
|||||||
report = "这就是获取到的天气情况了"
|
report = "这就是获取到的天气情况了"
|
||||||
|
|
||||||
for r in receivers:
|
for r in receivers:
|
||||||
robot.sendTextMsg(r, report)
|
robot.sendTextMsg(report, r)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
robot = Robot(WxSDK, Config())
|
wcf = Wcf()
|
||||||
|
|
||||||
# 初始化机器人
|
def handler(sig, frame):
|
||||||
robot.initSDK()
|
wcf.cleanup() # 退出前清理环境
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, handler)
|
||||||
|
|
||||||
|
robot = Robot(wcf)
|
||||||
|
robot.LOG.info("机器人已启动")
|
||||||
|
|
||||||
# 接收消息
|
# 接收消息
|
||||||
robot.enableRecvMsg()
|
robot.enableRecvMsg()
|
||||||
|
|||||||
157
robot.py
Normal file
157
robot.py
Normal 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)
|
||||||
@@ -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.
@@ -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.
Reference in New Issue
Block a user