让原生 Android 与 iOS 也用上法定工作日闹钟

我从 Nexus 6P 开始就一直用 Google 家的手机了。

这个智能法定工作日闹钟之前也尝试过几次,但都是失败了。

前段时间突然又想到这个事情,就又整了整。

但这次,我成功了。

2023.12.27 更新:新增接口 API 自动更新部分。

2023.07.11 更新:补充 MacroDroid 条件的添加方式。

小试牛刀

这段是刚开始折腾的过程,仅限 Android 并且需要「拨号上网」。

想看新且通用的方法请在右侧目录选择「开袋即食」。

我在 Android 上一直都用 MacroDroid 来进行自动化控制,方便我偷懒。

在某个深夜躺在床上无聊的时候,我发现它可以读取我 Google 日历中的事件并以此为触发条件,于是灵感就这么来了。

整体的工作逻辑很简单:将法定节假日和调休日导入进 Google 日历中,然后使用 MacroDroid 读取对应的事件自动判断是否设置闹钟

那么首先,我们得先将数据喂进日历当中。

很快啊,在互联网上冲浪了几分钟,我就找到了有大佬做的现成的方案,中国节假日补班日历

然后打开 Google 日历网页版,左下角的 其他日历 处有一个加号,点击加号选择 通过网址添加 就可以把上面大佬做的日历给订阅进自己的日历中。

订阅日历

为了方便后期判断,我们需要将 节假日补班 拆成两个日历订阅。

节假日订阅链接:
https://www.shuyz.com/githubfiles/china-holiday-calender/master/holidayCal-HO.ics

补班订阅链接:

https://www.shuyz.com/githubfiles/china-holiday-calender/master/holidayCal-CO.ics?compStart=*&compEnd=*

这里补班(调休)需要用到全天事件,所以按照文档在后面追加参数,这种加参定制用法仅支持作者的国内订阅地址链接,需注意。

这样数据的准备就完成了。

接下来工作转到手机这边,在手机上打开 Google 日历,让订阅的数据同步一下。

个人在测试的过程中发现 MacroDroid 读不读得到日历和机器上的 Google 日历 App 有很大关系,而且也出现过重启手机后 MacroDroid 读不到日历事件的情况,需要多次打开 Google 日历 App 才能解决。

这个也是我为什么会继续优化的原因,具体优化方案见下一段。

然后打开 MacroDroid,正式的开始写宏了。

判断的逻辑很简单,在周一到周五期间排除掉处在节假日日历中的日子,以及在周六周日期间拎出处在补班日历中的日子,这些日子让 MacroDroid 自动为手机设置闹钟,就可以达到智能法定工作日闹钟的目的了。

添加一个新宏,触发器 选择每天凌晨刚过几分的时间,动作 呢则是设置闹钟,想设置几个就设置几个。

<2023.07.11 更新>

条件添加方式为 MacroDroid 待定 - 与/或/异或/非 然后选择需要的条件。

如若在其中嵌套具体的逻辑时,在已添加的条件上点击,选择 添加约束 即可往其层级下添加其他条件。

约束条件 是重头戏,首先先设置一个 条件,然后在其中再设置两个 条件,第一个 条件下先设置条件 每周某天,勾选中周一至周五,再设置条件 日历条目,选择日历为 节假日,选项为 不在事件中;第二个 条件设置类似,先设置条件 每周某天,勾选中周六与周日,再设置条件 日历条目,选择日历为 补班,选项为 在事件中 即可。

是不是有点晕了?看下图就一目了然了。

日历条目选项

约束条件

修改了手机时间简单测试了一些日子,发现都能按照正常逻辑工作。

担心 MacroDroid 的触发器因为未知的原因不工作或错过,就多设置了几个触发时间,但又为了防止重复设置闹钟做无用功,于是在约束条件里再添加了一项检测自己最近一段时间内是否被调用,如果被调用就跳过,没调用就执行,算是避免了做无用功。

优化过的约束条件

深入优化

虽然东西能够正常的工作,但在使用的过程中发现能够干扰正常工作的因素还不少,并且有些原因至今都未能搞清楚。

稳定性不强的原因主要在于获取日历的事件上,那么能不能把这部分优化一下呢?

如果有这么一个接口,很直观的在返回结果里告诉设备今天是不是工作日,然后设备自己根据结果来决定是否设置闹钟,这样是不是稳定性更强一些呢?

思路有了,那么就动手干吧!

正好手上有一台腾讯云轻量无忧的机器,于是就萌生了利用这个机器开一个接口,将判断是否是工作的事情在服务器上做,手机则负责拿结果判断是否设置闹钟就可以了。

首先先找一份数据,于是我又花了点时间在互联网上冲浪,找到了另一位大佬做的免费接口 免费节假日 API

然后请出我的厉害同事法爷,帮我用 Node.js 写了个简单的 index.js,代码如下:

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
const Koa = require('koa');
const Router = require('@koa/router');
const data = require('./date.json')

const app = new Koa();
const router = new Router();


const formatNum = (num) => {
return num < 10 ? `0${num}` : `${num}`
}

const dateString = (today) => {
const month = today.getMonth() + 1;
const date = today.getDate();
return [`${formatNum(month)}-${formatNum(date)}`, today.getDay()];
}

router.get('/:date?', (ctx, next) => {
const [key, day] = ctx.params.date ? dateString(new Date(ctx.params.date)) : dateString(new Date());
const obj = data.holiday[key];
if (obj) {
if (obj.holiday) {
ctx.body = '1'
} else {
ctx.body = '0'
}
} else if (day === 6 || day === 0) {
ctx.body = '1'
} else {
ctx.body = '0'
}
});

app
.use(router.routes())
.use(router.allowedMethods());

app.listen(3007);

当然别忘了把数据放在同目录位置:

1
wget https://timor.tech/api/holiday/year/2023 -O date.json

如果服务器没有环境的话,直接安装个 nvm,借助 nvm 安装 Node.js:

1
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
1
nvm install --lts
1
npm i

来都来了,那再装个 PM2 来方便管理服务吧。

1
npm install pm2 -g

使用 PM2 来启动服务:

1
pm2 start index.js --name yasumi

再写个 Nginx 反代来暴露接口。

1
vim /etc/nginx/conf.d/yasumi.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 80;
server_name yasumi.neko7ina.com;
return 301 https://$server_name$request_uri;
}

server {
listen 443;
server_name yasumi.neko7ina.com;

ssl_certificate /home/syncthing/ssl/cert.pem;
ssl_certificate_key /home/syncthing/ssl/key.pem;

location / {
proxy_pass http://127.0.0.1:3007;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
}
}

这样,一个法定工作日接口就做好了。

现在你访问 https://yasumi.neko7ina.com/,返回结果是 1 表示休息,0 表示需要上班。

同时也支持后面加日期(仅限当年),方便调试,比如 https://yasumi.neko7ina.com/2023-5-6 返回结果就是 0,因为这一天需要上班。(呜呜呜呜……)

自动更新

<2023.12.27 更新>

新增接口 API 自动更新部分。

上面都是手动,想了想每年 12 月 31 号还得手动换一次数据也太麻烦了,那干脆写个脚本来搞吧。

直接新建一个脚本 update.sh 写如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash

current_year=$(date +"%Y")

new_year=$((current_year + 1))

source_file_path="/srv/www/yasumi/${new_year}.json"
destination_file_path="/srv/www/yasumi/date.json"
api_url="https://timor.tech/api/holiday/year/${new_year}"

wget "$api_url" -O "$source_file_path"

cp "$source_file_path" "$destination_file_path"

echo "$source_file_path 已成功复制并覆盖 $destination_file_path" >> /srv/www/yasumi/update.log

考虑到一年只执行一次输出信息就放同目录下了方便查看。

注意路径要换成项目实际存放的路径,别忘了给脚本执行权限:

1
chmod +x update.sh

然后使用 crontab 来按时间调用这个脚本:

1
57 23 31 12 * /srv/www/yasumi/update.sh

别急,还没完事,之前项目运行的时候没有监控目录,所以有变动是不会自动重载的。

虽然可以把重载命令写在脚本里,但既然 pm2 支持监控,那就使用这个特性好了。

先把运行中的项目停止并删除:

1
pm2 delete yasumi

然后重新开启服务并开启监控:

1
pm2 start index.js --name yasumi --watch

现在就完事啦!

开袋即食

现在再回来继续做宏就简单多了。

打开之前的宏,约束条件可以都删了只留一个检测自身最近是否有调用的条件,然后对 动作 进行大刀阔斧的改革。

先把设置闹钟的这一串动作保存到 代码块,方便后面直接调用减少重复劳动。

然后清空所有动作,添加动作 HTTP 请求(GET) ,网址填入接口的网址 https://yasumi.neko7ina.com/,下方三个选项全勾上,然后下面的 响应 选项中,选择 将 HTTP 响应保存在字符串变量中,此刻没有变量的话会提示新建一个变量,随便起个名字,这里我新建了个变量为 yasumi

HTTP 请求配置

再加一个动作等待 1 秒,然后上条件判断:如果变量 yasumi 的值为 0,则执行之前的设置闹钟动作块

这样的话,宏就完成了。

完整宏配置

抛弃了日历的调用后,现在单靠 MacroDroid 就可以做到了,这样其他的原生 Android 也可以轻松做到。

什么你问国内安卓?国内安卓这不标配功能吗还要整这个?

然后是朋友们的 iOS,也可以通过快捷指令来做到智能法定工作日闹钟了!

但是先别急,先去 iOS 自带的闹钟 App 中设定好自己需要的闹钟,记住重复选择「永不」

然后打开快捷指令 APP选择 自动化,添加一个新的自动化, 中设置时间,每天凌晨过几分,然后 执行 中的内容按下图即可。

快捷指令

这样 iOS 的智能法定工作日闹钟就完成啦!

写在最后

折腾了半天,最后的成果还是不错的。

这个接口大家想要使用都可以用,流量应该也不会有多少,人不多还是撑得住的吧。