自带内容源(草案 v1)
这是一份草案。 平台这一侧的接收还没有做——这份文档先把约定定下来, 因为约定错了,两边的代码都要重写。先写字,再写码。
这是什么
平台默认帮你保管一切:照片、影片、语音都放在我们的 R2 上。这对绝大多数人 是对的——不用管、不花钱、不会挂。
但你可以不这么做。
如果你愿意自己动手,你可以:
- 把你的媒体放在你自己的地方(你的对象存储、你的静态站、你的服务器)
- 用你自己设计的前端去呈现它们,怎么好看怎么来,我们不干涉
- 只把一份很小的清单交给平台
平台拿到清单之后,你的内容会正常出现在这里:能被搜到、能被送花、能被评论、 能被私信、能被邀请。别人完全看不出你的东西不在我们服务器上。
你交出去的只有「这条内容在哪里」,不是内容本身。
两个口子
这是最容易搞混的一件事,先说清楚。
你那边要开两个口,它们互不干扰:
| 口 | 给谁 | 长什么样 | 谁定 |
|---|---|---|---|
| 你的网站 | 人 | 网页,随便你设计 | 你 |
lyfee.json |
机器 | 一个固定格式的文本文件 | 这份文档 |
我们只读第二个。
为什么不能直接读你的网页:机器从网页里抠不出稳定的结构——你改一次版式, 我们就全断了。而清单文件的格式是死的,你的网页改一百次,我们照样接得上。
对一个会做网站的人来说,这不是额外负担:那份清单本来就是你渲染页面时 用的同一份数据,只是另存了一份。
清单文件
放在一个固定的、公开可读的 https 地址上,比如:
https://你的地址/lyfee.json
完整示例见同目录的 lyfee.example.json。
顶层
| 字段 | 必填 | 说明 |
|---|---|---|
version |
✅ | 固定写 1 |
alias |
✅ | 你在平台上的代号,要对得上 |
verify |
✅ | 平台给你的一串校验码,证明这个地址是你的 |
updated_at |
✅ | 这份清单最后一次变动的时间(ISO 8601,UTC) |
items |
✅ | 内容数组 |
每一条内容
| 字段 | 必填 | 说明 |
|---|---|---|
id |
✅ | 你自己定的稳定字符串。我们用它去重和更新 |
kind |
✅ | text / audio / blog / gallery 四选一 |
created_at |
✅ | 发布时间(ISO 8601,UTC)。决定它在时间线上的位置 |
caption |
正文(纯文字) | |
title |
只有 blog 用 |
|
tags |
字符串数组,主要给 blog 用 |
|
media |
媒体数组,见下 |
id 这一条要认真对待:它一旦定下来就别改。改了等于告诉我们「这是一条
新内容」,旧的那条会留在原地变成孤儿。用日期 + 短标题这种自己看得懂又不会
重复的字符串就行。
每一个媒体
| 字段 | 必填 | 说明 |
|---|---|---|
url |
✅ | https 直链 |
mime_type |
✅ | 比如 image/jpeg、audio/mp4 |
file_size |
✅ | 字节数。我们不下载你的文件,所以只能你报 |
thumbnail_url |
强烈建议 | 图片和影片的缩略图 |
width / height |
图片影片必填 | 像素 |
duration_ms |
音频影片必填 | 毫秒 |
peaks |
音频必填 | 波形,72 个 0–100 的整数 |
那几个「只有你能给」的数字
这是整份约定里最重要的一段。
我们不会下载你的文件——那就等于我们又在保管你的东西了,整件事就没有 意义了。可是不下载,有些东西我们就算不出来。这几样必须你给,而且你必须 真的算,不能随便填:
宽高(width / height)
没有的话,首页在图加载完的那一刻会跳一下,把下面的内容顶走。有了宽高, 我们能先按比例把位置占好。
缩略图(thumbnail_url)
首页不能直接加载原图。一张 8MB 的照片放在时间线上,手机会卡死。给一张 宽度 640 左右的小图就够了。
波形(peaks)+ 时长(duration_ms)
语音条上那一排高低不一的竖条,画的是这段录音真实的响度。
平台自己上传的语音是在浏览器里当场算的(见 lib/audio/peaks.ts)。你的
文件我们碰不到,所以只能你算。
算法必须一致,否则你的波形和别人的看起来是两种东西:
- 解码音频,只取第一个声道
- 把样本平均分成 72 段
- 每一段取最大绝对值(不是平均值——平均会把所有东西压成一条差不多高的 线,而人说话时的停顿和爆破音正是波形好看的原因)
- 找出这 72 个数里最大的那个,全体除以它再乘 100,四舍五入取整
第 4 步是整体归一化:录得小声的那一段也该看得见形状,不然手机随手录的 一段常常整条都贴着底。
我们不会去核对这份波形是不是真的来自那段音频——核对就要下载文件。所以 这一条靠自觉。填假的没人拦得住你,但那等于给自己的录音配了一个 不对的形状,骗的是听的人。
平台这边的规矩,你也要守
这些不是我们为难你,是内容进来之后要和别人的东西并排显示,得是同一套尺子。
哪一种能带什么媒体(这是产品决定,不是技术限制):
| kind | 能带 | 数量 |
|---|---|---|
text |
不带附件 | 0 |
blog |
不带附件 | 0 |
audio |
一个音频 | 1 |
gallery |
图片和影片 | 最多 10 |
正文长度上限:text / audio 各 280 字,gallery 2000 字,blog 50000 字。
「钉」这个动作在平台这边做,不在清单里。 钉住是上首页的唯一途径,而 它是稀缺的(免费 2 条)。你的内容进来之后,你自己在平台上决定钉哪一条—— 和所有人一样。图库钉不了:首页从来不收图库。
外部来源享受不到的三件事
说在前面,免得你做完才发现。
1. 加密码的私密内容(vault)
那是靠我们的门卫在放行的(签名地址、一次性链接、阅后即焚)。你的服务器上 没有那个门卫。外部来源只支持公开内容。
2. 转码
贴纸那套是把图片影片转成小尺寸动图,走的是我们域名下的转码服务,而代码里 有一道安全护栏:只许转我们自己域上的东西。放开等于开了一个公共代理, 任何人都能拿我们的域名去请求任意地址,日志里留的是我们的 IP。这道护栏 不会为外部来源拆掉。
3. 你挂了就是一个洞
你的服务器停了、域名到期了、桶删了——那条内容在首页上就是空的。
我们会显示一个诚实的状态(「这位作者的内容暂时取不到」),而不是一个碎图。 这既是对读者诚实,也是保护你:别人会知道是链路问题,不是你删了。
匿名怎么保
你把媒体放在自己的地方,就多出一条泄漏身份的路:域名的注册信息、DNS 记录、 证书日志都是公开可查的,而你的服务器还能看到每一个读者的 IP。
「自己的地方」不一定要是一台能被访问的服务器。 一个下载在你硬盘里的 文件包,也是「你的地方」,而且没有域名、没有 IP、什么都不泄漏——只是它 没法给别人看。这两件事要分开想。
如果确实要在线,读取方式打算给两种,你自己选:
| 方式 | 读者看到 | 你看到 | 代价 |
|---|---|---|---|
| 直连 | 你的域名 | 每个读者的 IP | 快,不花我们的钱 |
| 平台代取 | 我们的域名 | 只有我们的 IP | 慢一点,流量算我们的 |
代取的意思是过手不保管:读者向我们要,我们向你要,转手给出去,不落地、 不存。你的域名不会暴露给读者,读者的 IP 也不会暴露给你——两边都被挡住。
打算默认代取。要速度的人可以自己关掉。
怎么开始
先只测链路(今天就能做,平台这边不用动)
把几个文件放上去,用手机直接打开那些网址:图开得快不快、语音能不能拖 进度条、换到流量网络还行不行。
这一半通了再谈后面。不通就没必要往下走。
按这份文档生成
lyfee.json如果你用 AI 助手(Codex / Claude Code),直接把
AGENT_PROMPT.md整份喂给它,它会把上面这些事 一次做完,包括那几个必须真算的数字。等平台这边接上
接收还没做。做好之后这里会补上「怎么登记你的地址」和「发布了怎么通知 我们」两节。
待定的部分(我们这边还没做)
诚实列出来,免得你照着一份不存在的接口去写:
- 怎么登记清单地址、
verify校验码从哪里拿 - 发布之后怎么通知我们(会是「你敲门、我们去取」,不是我们定时轮询—— Vercel Hobby 的定时任务一天只能跑一次,靠轮询的话内容一天才更新一次)
- 取不到时的显示状态
- 代取那条路