隐藏日历中烦人的无关日程
众所周知,互联网公司是非常热爱开会的,且会议常常是非常低效 + 无聊的。作为一个互联网研发,我的日程表也不例外。
我的日历里面有很多重复的无效的日历,只要我在某个群,这个群被拉了会,那么这个会议日程就会在我的日历上展示,其实大概率我压根不用参加。就算拒绝会议要求,这个日程也会在我的日历上展示。如果想要完全不展示,那么就必须要完全退出这个日历,但是有些会议在某些时候还是需要参考的,所以不能完全退出。而且一个个点退出也很麻烦,尤其是偶尔会参加的重复性会议。
还有一些日程是自己用来提前占用自己的时间的虚假日程,例如我就配置了一些时间点作为专注的时间,这样 HR 就不会在这个时间点给我安排面试。
总之种种原因,我的日历上出现了无数的日程,而我又会把工作日历导入到滴答清单、苹果日历中。这个时候我的滴答清单日历上就会同时展示 Todo + 日历,日历基本上就没法看了,一点开花花绿绿的,但实际需要我参加的也没有多少。
所以我之前的操作经常就是把需要开的会议,单独记一个 Todo,工作的时候不看日历,看 Todo 就可以了。
但日历有日历的好处:
- 某时应该做某事:可以很直观的看到我在什么时间点,有什么事情要做
- 某时我做了某事:日程可以记录我在这个时间点做了什么,我的番茄钟也会在日历上显示
- 会议也会耗时:只用 Todo 的话,我经常会忘记在某个时间点还有会要开,导致时间任务分配出问题,最终事情做不完,自己反而会很内耗
- ...
所以一直有个想法,就是简单写一个 filter,把那些没用的日程给过滤掉,让我的日历在滴答清单上能真正的用起来。今天花了些时间,终于做了个简易版出来,算是勉强解决了我一个痛点问题,简单分享一下我的解决方案。目前这个方案除了程序员应该没人能用的起来...不过明天就要上班了,实在没时间折腾了,等有空再折腾一个易用版的。先看看最终效果:


留下来的基本上都是必须要参加的会,舒服多了!这个时候再去叠加 Todo 和番茄记录,就不会那么混乱了,起码都是自己确确实实想做、在做、已经做了的事情。

实现方案其实思路比较简单,因为飞书提供的是 CalDAV,需要自定义服务器、账号、密码,才能访问到具体日历信息,我搜了一圈,没有能过滤这种 CalDAV 的服务。
但如果你的日历服务可以直接提供 ics 文件订阅链接,那现在市面上应该是有现成的服务可以使用的,直接搜 ics filter 应该能有现成的服务。不过就算没有的话,实现一个也应该非常简单。
通过 Cloudflare Worker 或者 Vercel Function,获取 .ics 文件,再写点代码根据关键字过滤就可以了,非常简单!
我这个因为需要处理一下 CalDav 服务,所以麻烦一些,实现方案也比较粗糙,简单说下思路。
- 根据之前的 CalDAV 获取配置文件
- 因为 CalDAV 的获取时间比较长,我本地测试已经大于 10s 了,所以就没办法使用 Worker 来实现,因为免费的 Vercel 和 Cloudflare 的函数执行超时时间都设置在 10s
- 所以我这里是通过一段 Node 脚本,然后在 GitHub Action 中自动执行,配置 5 分钟自动执行一次,然后将文件存储到代码库中,我脚本是在公开的 repo 中执行的,ics 文件因为比较私密,我这里推送到了私有的 repo 中
- 通过 URL 获取 ics 文件
- 如果日历直接 push 到了公开的 GitHub repo 中,那么就非常简单了,直接通过这个文件的地址来作为 .ics 文件的订阅地址即可
- 但如果你像我一样,把这个放到了私有的 repo 中,就有些麻烦了,因为需要这个仓库的权限(token)才能获取到对应的文件
- 我最终是通过 Cloudflare Worker 的方式来获取这个文件的,worker 代码贴在下面,需要的也可以试一下
const GITHUB_TOKEN = '...............'; // 推荐放在环境变量中
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
const cacheKey = new Request(request.url, request);
const cachedResponse = await cache.match(cacheKey);
const url = new URL(request.url)
if (cachedResponse) {
return cachedResponse;
}
const githubAccount = '....'
const repo = '....'
const filePath = '....'
const githubApiUrl = `https://api.github.com/repos/${githubAccount}/${repo}/contents/${filePath}`;
const githubResponse = await fetch(githubApiUrl, {
headers: {
Authorization: `token ${GITHUB_TOKEN}`,
Accept: 'application/vnd.github.v3.raw',
'User-Agent': 'cloudflare-worker/1.0'
}
});
if (!githubResponse.ok) {
const errorText = await githubResponse.text();
console.log('GitHub error status:', githubResponse.status);
console.log('GitHub response body:', errorText);
return new Response('Failed to fetch from GitHub', { status: githubResponse.status });
}
if (!githubResponse.ok) {
return new Response('Failed to fetch from GitHub', { status: githubResponse.status });
}
const raw = await githubResponse.text();
const response = new Response(raw, {
status: 200,
headers: {
'Content-Type': 'text/calendar',
'Cache-Control': 'public, max-age=300', // 本地缓存
}
});
// ⏱ 设置边缘缓存 5 分钟(也支持按状态码)
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
}
};
其实如果能有个自己的服务器,就超级简单了,机器上跑一个 Node 服务,简单做下缓存,很轻松的就可以实现这个能力,压根不需要这么折腾。不过我懒得买一个服务器了,就用了 Cloudflare Worker 来做了下通过 token 获取。
等我找时间给朋友们安利安利,他们如果都觉得好用的话,我就搞个服务器弄一下好了,哈哈哈~
最后贴个 GitHub Action 来实现的代码,有需要的可以自行 Fork~ 对了,仓库一定要设置成 public 哦,因为 Github 对 Action 有 3000 分钟的限制,如果想 5 分钟跑一次的话,不知道 private 仓库顶不顶的住。
最后把 .ics 文件 push 到私有仓库就可以了~
**WARNING!!!!
当天晚上凌晨两点紧急更新!
最终选择不折腾了,直接搞个服务器部署是最最最方便的!代码我也扔上面的仓库上了。不搞上面的黑魔法了,各种问题...
读者看到这里的想自己试一试的,也最好自己搞个服务器部署上吧 🤣
方法也很简单,买个阿里云 99 块的小水管,然后 node, npm, git, pm2 一装。
然后记得去服务器配置那边,把发布的端口打开,不然用 ip 是无法访问的。然后 pm2 start server-version.mjs 就可以了。
对了,记得在自己的服务器上建一个 .env 文件,然后把配置放进去:
USERNAME=...
PASSWORD=...
URL=...就这样吧,心累了🤣🥲🥲,前面的什么 GitHub Action、Worker 都只能说白折腾。不过也不能这么说,熟悉了一下工具也是收获,哈哈哈哈~