前言

起因是想做一个能够每天定时在群里发送一些工作通知和预警。钉钉上有基于webhook的机器人,可是钉钉的使用频率远不及微信。所以想着在微信上实现一个自动通知的机器人。

效果

定时在群里发送今日值班通知,并且@相关人员:

记录备忘并且按时提醒:

选用方案

查找资料后发现,由于腾讯对web登录的限制,大部分基于web的方案都失效了,目前可以实现微信机器人的方式有hook和模拟gui操作。

出于安全性和稳定性角度我选择了使用gui方式,github上有人分享了思路 cluic/wxauto,我们可以直接站在前人的肩膀上针对自己的业务流程进行开发。

准备工作

下载微信的最新版本客户端,copy一份v3版本的代码到本地。

本方案的原理是模拟GUI点击,所以还需要一台24小时运行的服务器以及一个始终保持登录状态的微信小号。

实现值班通知

首先从数据库中获取今日值班表

from MysqlDb import MysqlDb

def sendTodaySche():
mysqldb = MysqlDb("schedule")
thisMonth = datetime.today().__format__("%Y-%m")
today = datetime.today().__format__("%m-%d").split('-')

result = mysqldb.sql_select("SELECT * FROM `" + thisMonth + "` WHERE date = '" + today + "'")["data"]
msgs = ["今日值班人员如下:"]
for i in range(0,len(tempMsgs)):
if '、' in result[0][i+2]:
splitStrs = result[0][i+2].split("、")
temp = []
for k in range(0,len(splitStrs)):
if i == 0 and k == 1:
temp.append("、"+splitStrs[k])
else:
temp.append("@"+splitStrs[k])
temp[0] = tempMsgs[i]+temp[0]
msgs.append(temp)
else:
msgs.append(tempMsgs[i] + '@' + result[0][i+2])

从数据库中获取并整理的是一串数组。其中,如果该班次为单人,则格式为“@人名” ,如果某个班次有两名以上的值班人员,则格式为数组“[‘@人名’,‘@人名’]”。

加上“@”的目的是在群聊中能够@到相对应的人员,是一个强提醒。

获取完数据之后需要向群聊中发送消息,但wxAuto库中的默认方法并不适合我的需求。

观察微信pc版发送消息的逻辑可知,发送换行消息需要按下“shift+enter”键,如果要@某人,则需要在输入人名后再次按下“enter”键才会成功,所以需要增加一个方法,如下:

def SendWrapAndATMsg(self,msgs, clear=True):
'''向当前窗口发送换行消息和@消息
msgs : 要发送的消息列表
clear : 是否清除当前已编辑内容
'''
self.UiaAPI.SwitchToThisWindow()
if clear:
self.EditMsg.SendKeys('{Ctrl}a', waitTime=0)
for i in range(0,len(msgs)):
if type(msgs[i]) ==type([]):
for k in range(0,len(msgs[i])):
self.EditMsg.SendKeys(msgs[i][k], waitTime=0)
if i == 1 and k == 1:
pass
else:
self.EditMsg.SendKeys('{Enter}', waitTime=0)
self.EditMsg.SendKeys('{Shift}{Enter}', waitTime=0)
else:
self.EditMsg.SendKeys(msgs[i], waitTime=0)
if i > 1:
self.EditMsg.SendKeys('{Enter}', waitTime=0)
self.EditMs备g.SendKeys('{Shift}{Enter}', waitTime=0)
self.EditMsg.SendKeys('{Enter}', waitTime=0)

上述新增的方法可实现我的需求,所以在`sendTodaySche()`方法中继续添加如下代码,调用我们新增的方法:

wechat = WeChat()
wechat.ChatWith("群聊名称")
wechat.SendWrapAndATMsg(msgs=msgs)

接着使用schedule库实现每日定时通知:

schedule.every().day.at("07:29").do(sendTodaySche)
schedule.run_pending()

实现备忘通知

记录备忘

首先需要定义一个方法持续监听与自己对话框。

def listening(who = '东东'):
wechat = WeChat()
wechat.ChatWith(who)
lastMsg = []

While True:
newMsg = wechat.GetLastMessage
if newMsg[0]=='东东' and lastMsg != newMsg:
pass
time.sleep(1)

在数据库中建立一个表格用于记录提醒事项

  • Id 索引
  • reminder 事项名称
  • todoDate 提醒日期
  • todoTime 提醒时间
  • isDone 是否已经提醒
  • earlyReminder 提前通知

我定义的提醒的消息格式为:

<日期><时间>【提醒我】<干什么事情>

比如:

  • 明天上午9点提醒我买菜
  • 1月23日17点20分提醒我收拾行李

首先要按照我们的需求对接受的消息进行格式化:

# 格式化消息
def formatMsg(msgs):
'''格式化消息
return:事项、日期、时间
'''
datestr1=["今","明","后天","大后天"]
splitMsgs = msgs.split("提醒我")
nowDate = datetime.today().date()
todoDate = str(nowDate + timedelta(days=0))
todoTime = "08:00"
todoThing = splitMsgs[-1]
for i in range(0,len(datestr1)):
if datestr1[i] in splitMsgs[0]:
todoDate = str(nowDate + timedelta(days=i))
break
reDate = re.search(r'\d{1,2}月\d{1,2}',splitMsgs[0])
if reDate != None:
nowYear = str(datetime.today().date().year)
temp = str(reDate.group(0)).split("月")
if len(temp[0]) == 1:
temp[0] = '0'+temp[0]
if len(temp[1]) == 1:
temp[1] = '0'+temp[1]
todoDate = nowYear+"-"+temp[0]+'-'+temp[1]

reTime = re.search(r'\d{1,2}点\d{1,2}',splitMsgs[0])
if reTime != None:
temp = str(reTime.group(0)).split('点')
if len(temp[0]) == 1:
temp[0] = "0"+temp[0]
if temp[1] == '':
temp[1] = "00"

if ("下午" in splitMsgs[0]) or ("晚" in splitMsgs[0]) or (todoDate == str(nowDate + timedelta(days=0)) and int(temp[0])<int(datetime.today().time().hour)):
temp[0] = str(int(temp[0])+12)
todoTime = temp[0]+":"+temp[1]
else:
reTime2 = re.search(r'\d{1,2}点',splitMsgs[0])
if reTime2 != None:
temp = str(reTime2.group(0)).split('点')
if len(temp[0]) == 1:
temp[0] = "0"+temp[0]
if ("下午" in splitMsgs[0]) or ("晚" in splitMsgs[0]) or (todoDate == str(nowDate + timedelta(days=0)) and int(temp[0])<int(datetime.today().time().hour)):
temp[0] = str(int(temp[0])+12)
todoTime = temp[0]+":00"

return [str(todoThing),str(todoDate),str(todoTime)]

将格式化后的字符串存进数据库:

def writeReminder(msgs):
msglist = formatMsg(msgs)
mysqldb = MysqlDb()
mysqldb.sql_execute("INSERT INTO reminder (reminder,todoDate,todoTime,isDone) VALUES(%s,%s,%s,%s)",msglist+['0'])
return msglist

这样就实现记录备忘的功能。

按时提醒

查找数据库中的提醒事项:

todoList = mysqldb.sql_select("SELECT Id,reminder,todoDate,todoTime,earlyReminder FROM reminder WHERE isDone = 0")["data"]

对返回的数据进行判断:

for i in range(0,len(todoList)):
if todoList[i][4] == "0" and nowDate == todoList[i][2] and (todaytime+timedelta(minutes=3)).__format__("%H:%M")==todoList[i][3]:
wechat.SendMsg("离 "+todoList[i][1]+" 还有三分钟,请做好准备!")
mysqldb.sql_execute("UPDATE reminder SET earlyReminder = 1 WHERE Id = '"+str(todoList[i][0])+"'")
if nowDate == todoList[i][2] and nowTime == todoList[i][3]:
wechat.SendMsg("记得"+todoList[i][1]+"哦!")
wechat.SendMsg("记得"+todoList[i][1]+"哦!")
wechat.SendMsg("记得"+todoList[i][1]+"哦!")
mysqldb.sql_execute("UPDATE reminder SET isDone = 1 WHERE Id = '"+str(todoList[i][0])+"'")

查看备忘和删除备忘

def deleteTask(taskId):
mysqldb = MysqlDb()
result = mysqldb.sql_execute("DELETE FROM reminder WHERE Id = "+str(taskId))
return result


def findAllUndo():
mysqldb = MysqlDb()
todoList = mysqldb.sql_select("SELECT Id,reminder,todoDate,todoTime FROM reminder WHERE isDone = 0")["data"]
msgs=[]
for i in range(0,len(todoList)):
msgs.append("备忘"+str(todoList[i][0])+":"+str(todoList[i][1])+" "+str(todoList[i][2])[5:]+" "+str(todoList[i][3]))
return msgs

设置关键词

只有在监听到相应的关键词时,机器人才会做出反应。

if "提醒我" in newMsg[1]:
tempMsg = re.search(r'\d{1,2}点',newMsg[1])
if tempMsg == None:
wechat.SendMsg("提醒时间未说明,请重试!")
continue
if int(str(tempMsg.group(0)).split("点")[0]) >=24 or int(str(tempMsg.group(0)).split("点")[0]) < 0:
wechat.SendMsg("时间设置有误!")
continue

result = writeReminder(newMsg[1])
newDates = result[1].split("-")
newTimes = result[2].split(":")
reminderDate = newDates[1]+"月"+newDates[2]+"日"
reminderTime = newTimes[0]+"点"+newTimes[1]+"分"
wechat.SendMsg("好的!我会在"+reminderDate+reminderTime+"的时候提醒你")

elif newMsg[1] == "所有备忘":
msgs = findAllUndo()
if msgs == []:
wechat.SendMsg("目前没有需要提醒的事情了。")
wechat.SendWrapMsg(msgs=msgs)
elif "删除备忘" in newMsg[1]:
taskId = newMsg[1][4:]
res = deleteTask(taskId)
if int(res) == 1:
wechat.SendMsg("删除成功!")
wechat.SendWrapMsg(findAllUndo())
else:
wechat.SendMsg("没有这个备忘!")

接入青云客机器人

这是一个聊胜于无的功能,青云客是一个免费无需登录的机器人,向接口发送信息即可获取机器人的回答。

def chatWithQingYunKe(msg):
url = 'http://api.qingyunke.com/api.php?key=free&appid=0&msg={}'.format(urllib.parse.quote(msg))
html = requests.get(url)
rt = html.json()["content"]
rt = rt.replace("菲菲","东东bot")
if "{br}" in rt:
rt = rt.split("{br}")
return rt

结语

本文介绍了如何使用微信制作机器人,并且实现定时发送值班通知和备忘提醒功能,功能比较基础,这里仅作抛砖引玉。下一篇文章将会实现微信机器人与一款超好用的笔记软件-trilium的联动,没有使用过trilium的,可以前往这篇文章了解一下,我理想中的笔记软件 - trilium | 东东的小黑盒