关于 google-calendar-sync
google-calendar-sync 是用于在预约系统中实现或审查 Google Calendar 同步的技能。它将忙碌时段导入与预约事件写出分开,并提供 OAuth、存储、通知、频道和测试方面的指导。
主要功能
- 01通过完整时间窗重同步导入忙碌时段
- 02使用确定性 ID 写入预约事件
- 03处理 OAuth 连接与加密令牌
- 04续订监听频道并安排重同步
- 05提供 Firestore 和 Supabase 存储选项
- 06涵盖测试与运维指导
适用场景
- 01根据所选日历阻止主持人的可预约时段
- 02将已确认预约添加到主持人日历
- 03审查重复、过期或停止的同步
- 04在 Firestore 与 Supabase 之间迁移同步存储
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ timerise-ai/ google-calendar-sync --skill google-calendar-sync安装后在 Claude Code 中输入/google-calendar-sync确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.mdREADME.mdCLAUDE.mdLICENSE.gitattributes.github/workflows/agent-eval.ymlevals/prompts.mdreferences/booking-events.mdadaptation.mdbusy-sync.mdchannels-and-settings.mddata-model.mdhttp-routes.mdoperations.mdtest-suite.mdgoogle-api.mdoauth-and-tokens.mdfirestore-backend.mdsupabase-backend.mdprovenance.mdtesting.md详细介绍
Google 日历同步
两个方向,各有一条规则。导入:Google 是了解主持人其他日程安排的事实来源,因此忙碌时间段只是某个时间窗口的临时镜像,会通过完整重新同步重建,绝不手动编辑。 导出:预订存储是预订信息的事实来源,因此写入 Google 的事件使用根据预订生成的 ID,绝不会被读回并作为忙碌时间。模板及其测试正是通过保持两个方向相互独立来确保这一点。
适用场景
- 主持人连接 Google 日历,并选择会影响其可用时间的日历。
- 预订会显示在主持人的 Google 日历中,可选择附带 Meet 链接,取消后会消失。
- 审查现有的 Google 同步:重复项、残留项、同步停止或反复重连。
- 将同步从 Firestore 迁移到 Supabase/Postgres,或反向迁移。
不适用场景
- 生成可预订时段、扣除忙碌时间:使用
time-slot-generator。此技能会生成它所消耗的忙碌时间段。 - Outlook 或 Microsoft 365:使用
microsoft-calendar-sync,它在 Microsoft Graph 上采用相同的架构和记录结构。 - 显示日历 UI 或发送邀请:使用主持人自己的电子邮件;此方案写入事件,但不会发送 Google 邀请邮件。
- 无需按用户 OAuth 的只读可用时间查询:使用 Google 的免费/忙碌查询搭配服务账号和全网域委派,这是另一种设计。
架构
connect (PKCE, single-use state) -> callback -> encrypted tokens -> calendar rows
|
inbound Google -- X-Goog-* -> /notifications -> syncBusy (lease) -> list all pages -> toBusyBlock -> diffBusy -> busy blocks -> availability
^ ^
+-- watch channel <-- cron: renew new-before-old, resync every busy calendar
outbound booking confirmed -> pushBookingEvent -> insert id=bk<sha256> (409: update) -> Meet link back
booking cancelled -> removeBookingEvent -> delete by id (404/410 = done)
适配约定
adaptation.md 包含完整约定、主持人前置条件和工作顺序。重命名表见 data-model.md。
| 接口 | 此技能提供 | 宿主提供 |
|---|---|---|
| 身份验证 | authenticate(request): userId or null | 宿主的会话查询 |
| 数据 | Firestore 和 Supabase 上的 CalendarStore | 宿主的项目 |
| 预订 | BookingSource、pushBookingEvent、removeBookingEvent | 宿主预订状态流程中的调用 |
| 可用时间 | 每位用户的忙碌时间段、listHostBusy | 宿主的时段引擎(time-slot-generator) |
| HTTP | Web Request 和 Response 处理器 | Next.js 路由处理器,或 Express 适配器 |
| 后台任务 | 每 12 小时执行一次 renewChannels,defer 可选 | 宿主的调度器、after() |
重要事实
- 忙碌时间同步是对完整时间窗口重新同步,而不是事件流。 通知只表示“有内容发生了变化”。必须列出
[now, now + horizon)的所有页面并进行差异比较。列表不完整时必须中止,绝不能删除数据。 - 所有 ID 都是确定性的。 日历行、忙碌时间段和预订事件均由其所代表内容的哈希生成。因此,并发通知、重试和重复触发最终会收敛,而不会产生重复项。
- 自己的事件也会被读回来。 如果将预订写入同时启用了忙碌时间同步的日历,该事件就会被重新导入,除非它带有私有的
bookingId标记并被跳过。对于按席位计费的服务,这会导致群组活动在首个预订后就无法再预订。 - 只有频道保持活动状态时,Google 才会告知你发生了哪些变化。 频道会过期,通知不一定会重试,而且时间窗口会向前移动。定时任务会续期频道并重新同步每个忙碌日历;没有它,忙碌时间就会停止更新,而且不会报告此情况。
invalid_grant是一种状态,不是错误。 访问权限被撤销、六个月未使用,或处于“Testing”模式七天,都可能导致此状态。将连接标记为reauth_required并向用户显示;重新连接同一账号时保留所有设置。- 全天事件的范围从
start.date到不包含在内的end.date,两者均为日历时区的当地午夜。 定时事件包含时区偏移。导出时间点时要保留Z;去掉它会导致事件时间发生偏移。 - 空闲和已拒绝的事件不会阻塞。 Google 自己的空闲/忙碌查询会忽略它们。节假日日历是例外:仅在启用它们以便阻塞时才使用。
严格规则
1. 绝不要从 OAuth 回调 URL 获取用户 ID。 state 是服务器端单次使用、有效期为 10 分钟的记录的键。否则,任何人都可以把自己的日历关联到别人的账户。
2. 在通道令牌针对其所关联的通道 ID 验证通过前,绝不要信任通知。 端点是公开的;令牌是唯一的凭据。
3. 新通道建立前,绝不要停止旧通道。 如果新监视失败,日历就没有通道,也没有任何机制续期,其忙碌时段将停止更新,而 UI 却显示为“已开启”。
4. 绝不要以明文存储令牌。 刷新令牌可以长期读取和写入日历。
5. 绝不要让 Google 故障导致预订失败。 外发写入只记录日志并返回;预订仍然有效。
快速开始
- 阅读宿主项目并填写适配契约:adaptation.md。
- 创建 Google Cloud OAuth 客户端和授权同意屏幕: oauth-and-tokens.md。
- 复制类型定义并决定重命名方式:data-model.md。
- 复制 Google 客户端:google-api.md。
- 复制入站同步 busy-sync.md;外发同步 booking-events.md;生命周期管理 channels-and-settings.md。
- 选择存储后端:firestore-backend.md 或 supabase-backend.md。
- 挂载路由和 cron:http-routes.md。
- 复制测试套件,并在两种
TZ值下运行:testing.md、 test-suite.md。 - 为运维人员提供健康状态视图:operations.md。
- 正在修复现有集成?修复顺序见 provenance.md。
参考目录
| 场景 | 触发关键词 | 参考资料 |
|---|---|---|
| 接缝与工作顺序 | adapt, host probe, seam contract, non-negotiables | adaptation.md |
| 实体、存储契约、重命名 | LinkedCalendar, BusyBlock, WatchChannel, CalendarStore, mutateCalendar, version, compare-and-swap | data-model.md |
| 连接账户 | OAuth, PKCE, state, consent screen, scopes, refresh_token, invalid_grant, reauth, revoke, AES-256-GCM, Testing mode | oauth-and-tokens.md |
| 与 Google 通信 | fetch, calendarList, events.list, pageToken, events.watch, channels.stop, 409 duplicate, 410, rate limit | google-api.md |
| 忙碌时段同步 | toBusyBlock, transparency, declined, all-day, end.date, DST, diffBusy, lease, resyncRequested, horizon | busy-sync.md |
| 创建预订 | event id, base32hex, extendedProperties private, Meet, conferenceData, hangoutLink, sendUpdates, backfill | booking-events.md |
| 通道与开关 | startChannel, renewChannels, make-before-break, orphan channel, holiday calendar, syncBusy, syncBookings, planTransitions | channels-and-settings.md |
| 路由与 webhook | X-Goog-Channel-ID, X-Goog-Resource-State, sync, exists, 204, callback redirect, cron secret, Next.js, Express | http-routes.md |
| Firestore | firebase-admin, runTransaction, batch 500, calendarBusyBlocks, composite index, security rules | firestore-backend.md |
| Supabase / Postgres | schema.sql, RLS, service role, optimistic concurrency, 23505, PostgREST 1000 rows | supabase-backend.md |
| 验证 | node:test, memory store, fake Google, TZ=, mutation check | testing.md, test-suite.md |
| 运行 | health, stale, channel-expired, env vars, quotas, dashboards, runbook, ngrok, localhost | operations.md |
| 构建缘由 | audit, defect, kept deliberately, designed here, fix order | provenance.md |
此内容属于 Timerise Skills 索引的一部分,该索引列出了相关技能。
评论
支持 Markdown