首页
/ Cal.com 天气日历应用深度解析:用 Emoji 把 OpenWeatherMap 16 天预报装进你的日历

Cal.com 天气日历应用深度解析:用 Emoji 把 OpenWeatherMap 16 天预报装进你的日历

2026-09-09 12:53:00作者:温玫谨Lighthearted

导读

Cal.com 自托管调度平台内置了一款名为 "Weather in your Calendar"(天气日历)的应用,它把 OpenWeatherMap 提供的本地天气预报以 ⛅️ 🌧️ ☀️ 🌨️ 等 Emoji 图标直接渲染进你日常使用的日历订阅源中,让你在规划外拍、花园派对或周末旅行时无需切换 App 即可一眼看到未来 16 天的天气。本文以该应用的官方描述文档 DESCRIPTION.md 为主体骨架,结合仓库中该模块的 config.jsonapi/add.tsAppSettingsInterface.tsx 等源码,完整讲解其功能特性、安装原理、Webcal 订阅链接参数及二次配置方法。

桌面端日历周视图中天气 Emoji 展示效果

移动端日历视图中天气 Emoji 展示效果

一、这个应用解决什么问题

"把天气预报直接放进日历"是这款应用的核心理念。传统做法是:规划活动前打开天气 App 查预报,再回到日历确认日期——两个信息源互相割裂。该应用将两者合并为单一信息流:

  • 天气数据直接嵌入日历的日期条目中,以 Emoji 图标 + 温度数值呈现;
  • 预报周期为 16 天(桌面端截图展示周视图,移动端宣传文案则提及 14 天预报能力,不同终端展示粒度略有差异);
  • 数据源为 OpenWeatherMap 的公开天气接口;
  • 用户只需输入所在城市并完成订阅,即可在几乎所有支持日历订阅的客户端上看到天气。

这正是它被收录进 Cal.com App Store 的原因:Cal.com 生态以「调度基础设施」为核心,而天气日历作为一种日历增强类应用(variant: "other"categories: ["other"]),在不改变调度逻辑的前提下丰富了日历的上下文信息,属于 packages/app-store 目录下众多可插拔应用中的一员。

二、功能特性拆解(对应官方描述)

官方 DESCRIPTION.md 用一段话概括了核心能力,展开后包含以下要点:

特性 说明
天气预报直入日历 无需额外打开天气 App,日历条目即天气条目
Emoji 可视化 以 ⛅️ 🌧️ ☀️ 🌨️ 等图标区分晴、雨、多云、雪等天气状态
16 天预报 基于 OpenWeatherMap 的多日预报数据
城市自定义 输入你的城市即可生成专属订阅源
偏好可调 支持摄氏度/华氏度等参数调整(源码中体现为 units 参数)
标准订阅协议 通过 webcal:// 链接完成日历订阅,兼容绝大多数日历客户端

两张官方效果图中:图 1 展示桌面端 Mac 风格日历(Week 视图),June 的每日条目旁出现 ☀️ 图标与 23°、14° 等温度,并标注了 Paris、New York 等城市;图 2 展示移动端日历(August 日期列表),条目旁同样带天气 Emoji 与温度,同时列出 "Search for your local city""Celsius and Fahrenheit (°C & °F)""Dark mode""14 day forecasts" 等能力点。两张图共同印证了「跨桌面/移动端、跨日历客户端」的通用订阅式工作方式。

三、应用元数据:从 config.json 看一个 App 的"身份证"

每个 Cal.com App 都由一个 config.json 描述其注册信息,天气日历的配置如下:

{
  "name": "Weather in your Calendar",
  "slug": "weather_in_your_calendar",
  "type": "weather_in_your_calendar_other",
  "logo": "icon.svg",
  "url": "https://github.com/vejnoe",
  "variant": "other",
  "categories": ["other"],
  "publisher": "Andreas Vejnø Andersen",
  "email": "info@vejnoe.dk",
  "description": "Get the local weather forecast with icons in your calendar",
  "__createdUsingCli": true,
  "isOAuth": false
}

字段语义与实现影响:

  • slug:全局唯一标识 weather_in_your_calendar,文件头注释明确提示"Don't modify slug",如需变更必须通过 CLI 的 edit 命令,避免破坏既有安装记录;
  • typeweather_in_your_calendar_other,用于在数据库 Credential 表中标记凭据类型(见下文安装原理);
  • variant / categories:均为 other,表示它不属于日历/视频/支付等标准分类,而是通用工具类应用;
  • logo:指向 icon.svg,一个橙黄色渐变底、含太阳与云朵图形的方形图标;
  • isOAuth: false:说明该应用不需要 OAuth 授权流程,是"零凭据"的纯订阅型应用;
  • urlpublisheremail:记录开发者归属信息(此处为第三方作者 Andreas Vejnø Andersen)。

该元数据被 App Store 的生成模块统一引用:在 apps.metadata.generated.ts 中可以看到 weather_in_your_calendar_config_json 被导入并注册进总元数据表,apps.browser.generated.tsxapps.server.generated.ts 也同步收录,从而在用户端的 App 市场列表与服务端安装校验中同时生效。

四、安装原理:声明式 Handler 与"默认安装"

天气日历不需要任何 API Key 或 OAuth 凭据,因此其安装逻辑极简。入口 index.ts 只做了一件事:

export * as api from "./api";

api/add.ts 定义了一个声明式(Declarative)安装处理器:

import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
import { createDefaultInstallation } from "../../_utils/installation";
import appConfig from "../config.json";

const handler: AppDeclarativeHandler = {
  appType: appConfig.type,
  variant: appConfig.variant,
  slug: appConfig.slug,
  supportsMultipleInstalls: false,
  handlerType: "add",
  createCredential: ({ appType, user, slug, teamId }) =>
    createDefaultInstallation({ appType, user: user, slug, key: {}, teamId }),
};

export default handler;

关键点解读:

  • handlerType: "add" 表明这是"点击安装"类型的处理器,api/index.ts 同目录的 api/index.ts 将其导出为 add
  • supportsMultipleInstalls: false 意味着每个用户只能安装一次(重复安装会被 installation.ts 中的 checkInstalled 以 HTTP 422 "Already installed" 拒绝);
  • createDefaultInstallation 来自通用工具 packages/app-store/_utils/installation.ts,它会向数据库写入一条 Credential 记录:typeweather_in_your_calendar_otherappId 为 slug,key 为空对象 {},并关联当前 userIdteamId。天气应用不需要存任何密钥,因此 key 为空即可。

这一设计体现了 Cal.com App Store 的通用抽象:无论应用多么简单(如本应用)或复杂(如 Stripe、Zoom 等含 OAuth 的支付/视频应用),都统一收敛到"安装时创建 Credential 记录"这一模式,业务差异完全由各 App 自己的 Handler 决定。

五、设置界面与 Webcal 订阅链接:参数逐个拆解

安装后,用户会在设置界面输入城市并生成订阅链接。前端实现位于 components/AppSettingsInterface.tsx

import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui/components/button";
import { TextField } from "@calcom/ui/components/form";

export default function AppSettings() {
  const { t } = useLocale();
  const unit = "metric";
  const [location, setLocation] = useState("");

  return (
    <div className="stack-y-4 text-sm">
      <TextField
        placeholder="San Francisco"
        value={location}
        name="Enter City"
        onChange={async (e) => {
          setLocation(e.target.value);
        }}
      />
      <Button
        href={`webcal://weather-in-calendar.com/cal/weather-cal.php?city=${location}&units=${unit}&temperature=day`}>
        {t("add_to_calendar")}
      </Button>
    </div>
  );
}

组件行为与参数说明:

参数 取值(源码现状) 含义
city 用户在输入框填写的城市名,占位符示例为 "San Francisco" 决定天气数据的地理位置
units 当前硬编码为 metric 温度单位制,metric 即摄氏度;官方宣传中同时提及 °C 与 °F 两种支持
temperature 固定为 day 温度取值口径为白天气温
协议 webcal:// 标准日历订阅协议,用于把远程 iCalendar 源同步进本地日历
服务端点 weather-in-calendar.com/cal/weather-cal.php 第三方天气订阅服务端,负责把 OpenWeatherMap 数据转换为日历源

交互逻辑:用户在文本框中输入城市(如 Paris),点击 "Add to calendar"(文案来自 i18n 键 add_to_calendar)后,浏览器会打开类似下面的订阅链接:

webcal://weather-in-calendar.com/cal/weather-cal.php?city=Paris&units=metric&temperature=day

手机端(iOS/Android)会直接唤起系统日历订阅;桌面端日历客户端(macOS Calendar、Outlook 等)同样支持该协议,从而实现"订阅一次、长期自动更新"。由于天气是定时拉取的日历源,未来 16 天内的预报会随订阅源同步刷新,这正是 DESCRIPTION.md 所述"enter your city, adjust according to your preferences and subscribe to your calendar"的完整闭环。

六、从源码结构看可扩展点

虽然当前前端把 units 固定为 metric,但 URL 参数本身已预留单位切换能力(截图宣传文案也明确支持 °C / °F)。若需要华氏度,只需将订阅链接中的 units 改为 imperial 即可获得不同单位制的预报数据——这一结论可从 URL 构造逻辑直接推断。同理,temperature=day 之外理论上还存在其他取值口径,具体以第三方服务端 weather-in-calendar.com 的实现为准,仓库内未包含服务端代码。

从仓库整体视角看,该应用是 Cal.com "低门槛接入"理念的典型样本:一个 package.json(仅依赖 @calcom/lib)、一个 config.json、一个声明式安装 Handler、一个设置组件,即可完成从 App Store 上架到用户安装配置的完整链路。开发者若想为 Cal.com 贡献同类轻量应用,完全可以以此为最小模板——先复制天气日历的目录骨架,替换 config.json 的元数据与设置组件,再在 apps.metadata.generated.ts 等生成文件中注册,即可让新应用出现在 App 市场中。

七、小结

Weather in your Calendar 用最轻量的方式解决了"日历里看天气"的诉求:Emoji 降低阅读成本,16 天预报覆盖短中期规划需求,webcal:// 订阅协议保证跨客户端可用性。在 Cal.com 的 App Store 体系里,它展示了一条不需要 OAuth、不需要密钥的"声明式安装"路径,配合 installation.ts 的统一 Credential 落库逻辑,让第三方开发者可以用极低成本为调度生态贡献日历增强能力。如果你想在自己的 Cal.com 实例上体验:在 App Store 中找到 "Weather in your Calendar",安装后在设置中填入城市,点击订阅即可在你的日历客户端中看到属于那座城市的 ☀️ 与 ⛅️。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395