0%

钉钉机器人实现自定义闹钟提醒


又是很长一段时间没写博客了,最近工作繁忙,加班多,也是无奈啊~但是好习惯还是应该坚持下去的,平时工作钉钉作为主要的沟通工具,发现它除了是个聊天软件以外,还有一个好玩的东西-钉钉机器人,做些自动化推送提醒还是不错的,之前在telegram看到过类似的东西,它有一套专属api,可发送一些自定义的消息,实现一些自动化功能,目前推送提醒的解决方案一般是app推送,短信,企业微信,邮件,为了推送提醒单独开发一个app成本太高,短信现在几乎都是收费的,企业微信注册麻烦,而邮件一般都不会及时去看的。钉钉机器人创建成本低,又是主力聊天工具之一,对于个人或群组推送还是很实用的,随便拉两个人创建一个群就可以添加机器人了,如果只是做个人提醒的话,创建好后可以把这两个人T掉。唯一的限制是1秒最多发送20条~由于个人用iphone手机,本土化做得不好,节假日后补班那几天经常因为忘记定闹钟而睡过头,又不想下载第三方app,准备用家里有个树莓派做个人小型服务器,每天晚上定时跑一个python脚本,提醒我明天是否上班,如果上班提醒我设好闹钟,并请求天气预报,如果明天上班且下雨,提醒我闹钟提前半个小时,下雨早点出门不堵啊。


创建钉钉机器人

只要是个群组即可创建钉钉机器人,先拉两个人组成群组,在群组菜单中选择群组助手,添加机器人选择自定义机器人,创建的时候填写机器人名字,这里需要复制webhook的url链接,安全设置中可勾选自定义关键字,签名,ip地址,这里为了简单选择自定义关键字,设置为“提醒”,只要发送内容中带了关键字“提醒”即可。如果选择签名的话需要参考官方的签名算法,签名需添加到webhook的url上,如果选择ip地址的话,只有该ip地址的服务器才可以调用api发送消息。

钉钉机器人发送消息

发起post请求,参数为json格式,就是机器人发送的内容,请求的地址就是创建机器人的webhook,如果安全设置选择了签名的话要带上签名参数。

1
2
3
4
5
6
7
8
import requests
import json
def messageRobot(msg):
url = '你的webhook'
headers = {
'Content-Type': 'application/json'
}
requests.post(url, data=json.dumps(msg), headers=headers)

获取日期信息

找了一个免费api,http://timor.tech/api/holiday/info/{yyyy-MM-dd},{yyyy-MM-dd}为要查询的日期,请求结果如下:
如果该接口请求失败,code = 1,也需要给自己发送接口请求失败的消息,及时处理
type = 0, 明天是正常的工作日
request url:http://timor.tech/api/holiday/info/2019-10-11

1
2
3
4
5
6
7
8
9
{
"code": 0,
"type": {
"type": 0,
"name": "周五",
"week": 5
},
"holiday": null
}

type = 1, 明天是正常的双休日
request url: http://timor.tech/api/holiday/info/2019-10-13

1
2
3
4
5
6
7
8
9
{
"code": 0,
"type": {
"type": 1,
"name": "周日",
"week": 7
},
"holiday": null
}

type = 2, 明天是法定节假日
request url:http://timor.tech/api/holiday/info/2019-10-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"code": 0,
"type": {
"type": 2,
"name": "国庆节",
"week": 2
},
"holiday": {
"holiday": true,
"name": "国庆节",
"wage": 3,
"date": "2019-10-01"
}
}

type = 3, 明天是补班的特殊日子
request url:http://timor.tech/api/holiday/info/2019-10-12

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"code": 0,
"type": {
"type": 3,
"name": "国庆节后调休",
"week": 6
},
"holiday": {
"holiday": false,
"name": "国庆节后调休",
"after": true,
"wage": 1,
"target": "国庆节",
"date": "2019-10-12"
}
}

获取天气预报

也是找了一个免费Api,http://t.weather.sojson.com/api/weather/city/{citycode},参数是城市编码,以成都为例,请求结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
{
"message": "success感谢又拍云(upyun.com)提供CDN赞助",
"status": 200,
"date": "20200307",
"time": "2020-03-07 22:52:50",
"cityInfo": {
"city": "成都市",
"citykey": "101270101",
"parent": "四川",
"updateTime": "22:30"
},
"data": {
"shidu": "63%",
"pm25": 52.0,
"pm10": 79.0,
"quality": "良",
"wendu": "14",
"ganmao": "极少数敏感人群应减少户外活动",
"forecast": Array[15][
{
"date": "07",
"high": "高温 19℃",
"low": "低温 12℃",
"ymd": "2020-03-07",
"week": "星期六",
"sunrise": "07:25",
"sunset": "19:06",
"aqi": 94,
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "08",
"high": "高温 18℃",
"low": "低温 11℃",
"ymd": "2020-03-08",
"week": "星期日",
"sunrise": "07:24",
"sunset": "19:07",
"aqi": 57,
"fx": "无持续风向",
"fl": "<3级",
"type": "阴",
"notice": "不要被阴云遮挡住好心情"
},
{
"date": "09",
"high": "高温 17℃",
"low": "低温 8℃",
"ymd": "2020-03-09",
"week": "星期一",
"sunrise": "07:22",
"sunset": "19:08",
"aqi": 52,
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "10",
"high": "高温 18℃",
"low": "低温 8℃",
"ymd": "2020-03-10",
"week": "星期二",
"sunrise": "07:21",
"sunset": "19:08",
"aqi": 59,
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "11",
"high": "高温 15℃",
"low": "低温 9℃",
"ymd": "2020-03-11",
"week": "星期三",
"sunrise": "07:20",
"sunset": "19:09",
"aqi": 55,
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "12",
"high": "高温 17℃",
"low": "低温 11℃",
"ymd": "2020-03-12",
"week": "星期四",
"sunrise": "07:19",
"sunset": "19:10",
"aqi": 62,
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "13",
"high": "高温 15℃",
"low": "低温 11℃",
"ymd": "2020-03-13",
"week": "星期五",
"sunrise": "07:18",
"sunset": "19:10",
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "14",
"high": "高温 20℃",
"low": "低温 12℃",
"ymd": "2020-03-14",
"week": "星期六",
"sunrise": "07:16",
"sunset": "19:11",
"fx": "东北风",
"fl": "<3级",
"type": "阴",
"notice": "不要被阴云遮挡住好心情"
},
{
"date": "15",
"high": "高温 15℃",
"low": "低温 11℃",
"ymd": "2020-03-15",
"week": "星期日",
"sunrise": "07:15",
"sunset": "19:12",
"fx": "东北风",
"fl": "<3级",
"type": "阴",
"notice": "不要被阴云遮挡住好心情"
},
{
"date": "16",
"high": "高温 14℃",
"low": "低温 9℃",
"ymd": "2020-03-16",
"week": "星期一",
"sunrise": "07:14",
"sunset": "19:12",
"fx": "东北风",
"fl": "<3级",
"type": "阴",
"notice": "不要被阴云遮挡住好心情"
},
{
"date": "17",
"high": "高温 16℃",
"low": "低温 10℃",
"ymd": "2020-03-17",
"week": "星期二",
"sunrise": "07:13",
"sunset": "19:13",
"fx": "南风",
"fl": "<3级",
"type": "小雨",
"notice": "雨虽小,注意保暖别感冒"
},
{
"date": "18",
"high": "高温 20℃",
"low": "低温 9℃",
"ymd": "2020-03-18",
"week": "星期三",
"sunrise": "07:12",
"sunset": "19:14",
"fx": "南风",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "19",
"high": "高温 22℃",
"low": "低温 11℃",
"ymd": "2020-03-19",
"week": "星期四",
"sunrise": "07:10",
"sunset": "19:14",
"fx": "东南风",
"fl": "<3级",
"type": "阴",
"notice": "不要被阴云遮挡住好心情"
},
{
"date": "20",
"high": "高温 23℃",
"low": "低温 12℃",
"ymd": "2020-03-20",
"week": "星期五",
"sunrise": "07:09",
"sunset": "19:15",
"fx": "东北风",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
},
{
"date": "21",
"high": "高温 22℃",
"low": "低温 13℃",
"ymd": "2020-03-21",
"week": "星期六",
"sunrise": "07:08",
"sunset": "19:16",
"fx": "西北风",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
}
],
"yesterday": {
"date": "06",
"high": "高温 16℃",
"low": "低温 10℃",
"ymd": "2020-03-06",
"week": "星期五",
"sunrise": "07:26",
"sunset": "19:06",
"aqi": 79,
"fx": "无持续风向",
"fl": "<3级",
"type": "多云",
"notice": "阴晴之间,谨防紫外线侵扰"
}
}
}

如果请求成功status=200,data的forecast字段是包括今天以及未来14天的天气情况,所以这个字段下的第二个元素就是明天的天气预报,判断该元素下的type字段是否包含“雨”,并返回调用结果,如果接口请求失败也给自己发送一条消息,及时处理

python3实现

每天定时跑脚本,给自己发钉钉消息,并结合明天的天气预报,如果要下雨给自己发送要早起的提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import json
import requests
import datetime
import time
def getWeather():
url = "http://t.weather.sojson.com/api/weather/city/{citycode}"
response = requests.get(url)
logWithTime("getWeather ---> " + response.text)
json_data = json.loads(response.text)
status = json_data.get('status')
if status == 200:
# 明天的天气信息
tomorrow = json_data.get('data').get('forecast')[1]

# 明天的天气
tomorrow_weather = str(tomorrow.get("type"))
if "雨" in tomorrow_weather:
return 1
else:
return 0
else:
return -1

def getDateInfo():
#获得今天的日期
today = datetime.date.today()
#获得明天的日期
tomorrow = today + datetime.timedelta(days=1)
url = "http://timor.tech/api/holiday/info/" + str(tomorrow)
response = requests.get(url)
logWithTime("getDateInfo ---> " + response.text)
json_data = json.loads(response.text)
dayType = json_data.get('type').get('type')
code = json_data.get('code')
holiday = json_data.get('holiday')

if code == 1:
logWithTime("getDateInfoError ---> ")
requestError(True)
else:
if(dayType == 0): #正常上班
messageWorkNormal(getWeather())
elif(dayType == 1 or dayType == 2): #普通周末和节假日
messageHoliday()
elif(dayType == 3): # 补假日
messageWorkAbnormal(today, str(holiday.get('name')),getWeather())



# 休息日
def messageHoliday():
logWithTime("messageHoliday ---> ")
messageRobot({
"msgtype": "text",
"text": {
"content": "【闹钟提醒】明天休息日,不上班哦~"
}
})

# 正常上班
def messageWorkNormal(rain):
if rain == -1:
logWithTime("getWeatherError ---> ")
requestError(False)
else:
str = ""
if rain == 1:
logWithTime("messageWorkNormal rain ---> ")
str = "可能下雨,需提前出门,"
logWithTime("messageWorkNormal ---> ")
messageRobot({
"msgtype": "text",
"text": {
"content": "【闹钟提醒】明天要上班,"+ str +"注意添加闹钟!!!"
}
})


# 接口请求出错处理
def requestError(date):
str = ""
if date:
str = "日期信息"
else:
str = "天气信息"

messageRobot({
"msgtype": "text",
"text": {
"content": "【闹钟提醒】"+str+"接口请求失败,注意添加闹钟!!!"
}
})

# 补假要上班
def messageWorkAbnormal(date, reason, rain):
if rain == -1:
logWithTime("getWeatherError ---> ")
requestError(False)
else:
str = ""
if rain == 1:
logWithTime("messageWorkAbnormal rain ---> ")
str = "可能下雨,需提前出门,"
logWithTime("messageWorkAbnormal ---> ")
messageRobot({
"msgtype": "text",
"text": {
"content": "【闹钟提醒】明天是:{},{} 要上班,".format(date,reason)+ str + "注意添加闹钟!!!"
}
})


# 钉钉机器人发送消息
def messageRobot(msg):
url = 'your webhook'

headers = {
'Content-Type': 'application/json'
}
requests.post(url, data=json.dumps(msg), headers=headers)

# 日志带时间利于排查
def logWithTime(msg):
localtime = time.asctime( time.localtime(time.time()) )
print (localtime + " " + msg)


if __name__ == "__main__":
getDateInfo()

每日晚上11点23在服务端运行这个脚本,请求钉钉机器人接口,给自己发送一个钉钉推送提醒,要定好闹钟

树莓派设置定时任务

将python脚本上传到树莓派,用ftp,ssh,samba都可以,这里就不详细说明了,设置定时任务前需要注意的是校准树莓派的时间,树莓派默认采用欧洲时区,如果树莓派的时间校准过,可略过此步骤。

校准树莓派时间

查看当前树莓派时间date
设置树莓派时区
sudo dpkg-reconfigure tzdata
选择亚洲时区Asia,选择上海时间Shanghai

contab设置定时任务

linux定时任务可利用contab设置,crontab -e,进入文件编辑,选择编辑工具,nano或vim
格式为:Minute Hour Day Month Dayofweek command
Minute 每个小时的第几分钟执行该任务
Hour 每天的第几个小时执行该任务
Day 每月的第几天执行该任务
Month 每年的第几个月执行该任务
DayOfWeek 每周的第几天执行该任务
Command 要执行的命令
设置23点22分执行python3的脚本,并输出日志,利于维护和问题排查
22 23 * * * python3 /home/pi/upload/test123.py >>/home/pi/mylog.log
保存文件,并重启contab服务
sudo service cron restart

ip变化提醒(2020/03/07更新)

家里申请了电信的公网ip,之前买了域名,一直在用端口转发+动态域名服务访问家里的树莓派,最近域名过期了,不打算续了,但希望仍然能在外网访问到家里的树莓派,电信的公网ip一直都是变动的,让电信固定ip需要繁杂的手续且需要企业申请,所以是不可能的了。最后想了一个可行的方法,如果能利用钉钉机器人在ip变化时给自己发送一条消息,告知最新的ip,那么仍然可以访问到家里的树莓派,这个问题就解决了。那如何知道ip变化了呢?还是利用linux的定时任务,每个小时跑一次python脚本,获取当前的外网ip,并将第一次的结果写入本地文件记录下来,每次运行脚本将当前外网ip与上一次记录的外网ip对比,如果不一致则ip变化,给自己发送钉钉消息告知最新的ip并再次写入文件刷新本地记录,如果ip一致则不用发送。

获取本机外网ip

找了一个免费的接口,http://members.3322.org/dyndns/getip,直接返回本机外网ip,如果为空就请求失败了,也给自己发送钉钉消息,及时处理,python实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import json
import requests
import os
import time
# 获取当前ip
def getip():
url = "http://members.3322.org/dyndns/getip"
response = requests.get(url)
logWithTime ("getip --->" + response.text)
myip = response.text.strip()
return myip

# 接口请求出错处理
def requestError():
messageRobot({
"msgtype": "text",
"text": {
"content": "【ip变更提醒】获取ip接口请求失败!!!"
}
})

def writeFile(ip):
fo = open("ip.txt", "w")
fo.write( ip)

def readFile():
fo = open("ip.txt", "r+")
return fo.read()

# 钉钉机器人发送消息
def messageRobot(msg):
url = 'your webhook'

headers = {
'Content-Type': 'application/json'
}
requests.post(url, data=json.dumps(msg), headers=headers)

# 开启ip变更通知任务
def notifyIpTask():
if os.path.exists("ip.txt"):
lastIp = readFile()
else:
lastIp = ""
logWithTime("notifyIpTask ---> notify ip server start")
myIp = getip()
if myIp == "":
logWithTime("notifyIpTask ---> get ip error")
requestError()
elif myIp == lastIp:
logWithTime("notifyIpTask ---> same ip")
else:
logWithTime("notifyIpTask ---> send ip")
writeFile(myIp)
messageRobot({
"msgtype": "text",
"text": {
"content": "【ip变更提醒】当前ip为{}".format(myIp)
}
})

# 日志带时间利于排查
def logWithTime(msg):
localtime = time.asctime( time.localtime(time.time()) )
print (localtime + " " + msg)

if __name__ == "__main__":
notifyIpTask()