Bingline API:每天一行新的风景
最近整理 0a.ink 这个域名时,我顺手做了一个小服务:Bingline API。原因其实很简单,我想要一个稳定地址,能直接拿到 Bing 今天那张图,塞进 img 或 background-image 里就结束。
Bingline 这个名字就是 Bing + line。如果说 0a.ink 里的 0A 是换行,那么 wall.0a.ink 大概可以理解成每天更新的一行风景。它不打算做壁纸站,也不想维护一整套图片库。它做的事情就一件:把 Bing 首页当天的图片整理成几个好用的入口。
项目默认假设的域名是:
1 | |
最直接的用法也很简单:
1 | |
或者:
1 | |
这就是 Bingline API 最核心的用途:给每日壁纸一个短、稳定、可嵌入的入口。
为什么要做得这么轻
很多壁纸 API 走着走着就会长成一整套系统:存图、归档、数据库、后台、定时同步、搜索、收藏。这条路当然没问题,只是不是我这次想做的。
我想要的服务很克制:不存 Bing 图片,不建历史库,不引入 KV、R2、GitHub 仓库之类的同步层,也不把自己包装成新的内容平台。最好每个接口拿出来一看,就知道它是干什么的。
所以 Bingline API 最后被写成了一个 Cloudflare Worker。请求进来,它去访问 Bing 的 HPImageArchive.aspx,解析当天图片的元数据,再按调用者需要返回跳转、图片代理、纯 URL、整理过的 JSON,或者 Bing 的原始 JSON。
这样做有个很直接的好处:边界清楚。图片还是来自 Bing,版权也还是 Microsoft、原作者或相关权利方的;这个项目只是包了一层轻薄的外壳,没有假装自己是图片来源。
四个入口
仓库里现在有四个主要路由:
1 | |
其中 /latest 最适合前端直接用。默认情况下,它不会代理图片内容,而是返回一个 302 跳转,把浏览器带到解析出来的 Bing 图片地址。这样 Worker 不用自己扛大图流量,也更符合这个服务的轻量定位。
如果某些场景需要真正从 wall.0a.ink 返回图片,也可以使用:
1 | |
这时 Worker 会代理图片响应,并给图片设置更长的缓存时间。默认模式还是 redirect,因为大多数 HTML 和 CSS 场景其实没必要多绕这一层。
/api/latest 返回整理后的 JSON。它会把 Bing 原始字段转成更稳定的结构,比如日期、市场、标题、版权信息、当前服务的图片入口、解析出来的 Bing 图片 URL、实际分辨率和来源端点。这里有个我挺喜欢的小细节:image 字段指回 https://wall.0a.ink/latest?...,而 bingImageUrl 保存真正解析出来的 Bing 图片地址。前者适合继续走自己的服务入口,后者适合调试,或者需要明确来源的时候用。
/url 更干脆,只返回解析后的图片 URL。默认是 text/plain,如果加上 format=json,也可以得到:
1 | |
/raw 则保留 Bing 原始 JSON,适合那些想自己处理 HPImageArchive.aspx 数据的场景。它支持 n 参数,但最多限制到 8,免得这个小服务转头变成一个没有边界的批量转发入口。
参数是一层温和的约束
Bingline API 支持的参数不多:
mkt:Bing 市场,默认en-US。idx:相对日期索引,默认0。res:目标分辨率,默认UHD。mode:redirect或proxy。format:text或json。n:原始元数据数量,默认1,最大8。
这些参数没有被做成什么复杂查询语言,只是保留了 Bing 每日图接口里最常用的几个控制点。比如想看中文市场的今天图片,可以访问:
1 | |
想取昨天的英文市场图片,可以访问:
1 | |
想指定常见分辨率,可以访问:
1 | |
源码里对这些参数做了基础清洗:数字参数会被限制在合理范围,文本参数会去掉不属于简单 token 的字符,n 会被夹到最大值 8。这不是什么复杂安全框架,只是一个小服务该有的基本秩序。
分辨率 fallback
每日 Bing 图片并不总能直接拿到请求的分辨率。Bingline API 处理这件事的方法很务实:先基于 urlbase 构造候选地址,再逐个检查图片是否存在。
如果请求的是 UHD,候选顺序大致是:
1 | |
Worker 会先用 HEAD 检查候选图片是否存在。如果遇到不支持 HEAD 的响应,再退回到带 Range: bytes=0-0 的请求。这样一来,/latest 在 UHD 不可用时还能自然退回到 1920x1080 或原始图片,而不是直接报错。
这也是 /api/latest 里 resolution 字段存在的原因。调用者请求的是一个目标分辨率,但实际返回可能是 fallback 之后的结果。把这个信息直接暴露出来,总比假装一切都是 UHD 要诚实。
缓存、CORS 和错误
作为一个可能被网页直接引用的服务,Bingline API 默认给 JSON 和 text 端点加了 CORS:
1 | |
OPTIONS 预检请求会直接返回 204。这样它就能被别的小网页、小工具和实验页面直接调用,不必为了一个每日壁纸接口再单独补个后端。
缓存策略也很朴素:
/latestredirect 缓存 1 小时。/latest?mode=proxy缓存 1 天。/api/latest、/url、/raw缓存 1 小时。- 错误响应不缓存。
错误统一返回 JSON:
1 | |
这些选择都不复杂,但真组合起来以后,服务就会更像一个能长期放着的小基础设施:页面可以直接用,脚本也能读,失败时还比较容易看出哪里出了问题。
为什么放在 Cloudflare Worker 上
这个项目很适合 Worker,因为它的状态几乎为零。请求进来,取 Bing 元数据,解析图片 URL,返回结果。没有数据库连接,没有后台队列,没有本地文件,也没有部署后还要额外同步的资源。
仓库里的 wrangler.toml 也很短:
1 | |
开发命令同样保持简单:
1 | |
CI/CD 则放在 GitHub Actions:PR 到 main 时运行 npm ci、typecheck 和测试;push 到 main 时先验证,再通过 Wrangler 部署到 Cloudflare Workers。也就是说,这个服务虽然很小,但发布前至少还有一道像样的门槛。
测试覆盖的也都是容易真出问题的地方:默认参数、参数清洗、候选图片 URL 构造、Bing 元数据解析、分辨率 fallback、/latest 的 redirect 和 proxy、/api/latest 的标准化 JSON、/url 的 text/json 两种格式、/raw、不支持的 mode、预检请求和 404。
我挺喜欢这种小项目的测试状态。不是为了好看的覆盖率数字,而是把服务真正会坏掉的边界先围起来。
一个小服务的位置
wall.0a.ink 不是一个宏大的产品。它更像 0a.ink 这个域名体系里的一张小标签:今天的 Bing 风景,从这里拿。
它的重点也不在于“我拥有了一套壁纸 API”。恰恰相反,它一直在避免拥有太多东西:不存图片,不留历史,不建数据库,也不养复杂后台。它只把一件事做好:把每天的 Bing 图片整理成稳定的一行。
这和我对 0a.ink 的理解其实很一致。0A 是换行,ink 是留下。很多小工具不需要一开始就长成完整产品,它们只需要有一个短小、清楚、以后还能再打开的位置。
Bingline API 就是这样一行:
1 | |