Jin's blog

隐藏日历中烦人的无关日程

工具

众所周知,互联网公司是非常热爱开会的,且会议常常是非常低效 + 无聊的。作为一个互联网研发,我的日程表也不例外。

我的日历里面有很多重复的无效的日历,只要我在某个群,这个群被拉了会,那么这个会议日程就会在我的日历上展示,其实大概率我压根不用参加。就算拒绝会议要求,这个日程也会在我的日历上展示。如果想要完全不展示,那么就必须要完全退出这个日历,但是有些会议在某些时候还是需要参考的,所以不能完全退出。而且一个个点退出也很麻烦,尤其是偶尔会参加的重复性会议。

还有一些日程是自己用来提前占用自己的时间的虚假日程,例如我就配置了一些时间点作为专注的时间,这样 HR 就不会在这个时间点给我安排面试。

总之种种原因,我的日历上出现了无数的日程,而我又会把工作日历导入到滴答清单、苹果日历中。这个时候我的滴答清单日历上就会同时展示 Todo + 日历,日历基本上就没法看了,一点开花花绿绿的,但实际需要我参加的也没有多少。

所以我之前的操作经常就是把需要开的会议,单独记一个 Todo,工作的时候不看日历,看 Todo 就可以了。

但日历有日历的好处:

  1. 某时应该做某事:可以很直观的看到我在什么时间点,有什么事情要做
  2. 某时我做了某事:日程可以记录我在这个时间点做了什么,我的番茄钟也会在日历上显示
  3. 会议也会耗时:只用 Todo 的话,我经常会忘记在某个时间点还有会要开,导致时间任务分配出问题,最终事情做不完,自己反而会很内耗
  4. ...

所以一直有个想法,就是简单写一个 filter,把那些没用的日程给过滤掉,让我的日历在滴答清单上能真正的用起来。今天花了些时间,终于做了个简易版出来,算是勉强解决了我一个痛点问题,简单分享一下我的解决方案。目前这个方案除了程序员应该没人能用的起来...不过明天就要上班了,实在没时间折腾了,等有空再折腾一个易用版的。先看看最终效果:

😓 优化前

😆 优化后

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

✅ 搭配滴答清单中的任务&番茄记录

实现方案其实思路比较简单,因为飞书提供的是 CalDAV,需要自定义服务器、账号、密码,才能访问到具体日历信息,我搜了一圈,没有能过滤这种 CalDAV 的服务。

但如果你的日历服务可以直接提供 ics 文件订阅链接,那现在市面上应该是有现成的服务可以使用的,直接搜 ics filter 应该能有现成的服务。不过就算没有的话,实现一个也应该非常简单。

通过 Cloudflare Worker 或者 Vercel Function,获取 .ics 文件,再写点代码根据关键字过滤就可以了,非常简单!

我这个因为需要处理一下 CalDav 服务,所以麻烦一些,实现方案也比较粗糙,简单说下思路。

  1. 根据之前的 CalDAV 获取配置文件
    1. 因为 CalDAV 的获取时间比较长,我本地测试已经大于 10s 了,所以就没办法使用 Worker 来实现,因为免费的 Vercel 和 Cloudflare 的函数执行超时时间都设置在 10s
    2. 所以我这里是通过一段 Node 脚本,然后在 GitHub Action 中自动执行,配置 5 分钟自动执行一次,然后将文件存储到代码库中,我脚本是在公开的 repo 中执行的,ics 文件因为比较私密,我这里推送到了私有的 repo 中
  2. 通过 URL 获取 ics 文件
    1. 如果日历直接 push 到了公开的 GitHub repo 中,那么就非常简单了,直接通过这个文件的地址来作为 .ics 文件的订阅地址即可
    2. 但如果你像我一样,把这个放到了私有的 repo 中,就有些麻烦了,因为需要这个仓库的权限(token)才能获取到对应的文件
    3. 我最终是通过 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 都只能说白折腾。不过也不能这么说,熟悉了一下工具也是收获,哈哈哈哈~