Keep your media on your own side

You can host your own media and still publish here, be seen here, and be messaged here.

This guide is currently written in Chinese only.

Prompt for your AI assistant

Copy it and paste the whole thing into Codex or Claude Code — it will do the setup end to end.

Download .md

Example manifest: lyfee.example.json

自带内容源(草案 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/jpegaudio/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)。你的 文件我们碰不到,所以只能你算。

算法必须一致,否则你的波形和别人的看起来是两种东西:

  1. 解码音频,只取第一个声道
  2. 把样本平均分成 72 段
  3. 每一段取最大绝对值(不是平均值——平均会把所有东西压成一条差不多高的 线,而人说话时的停顿和爆破音正是波形好看的原因)
  4. 找出这 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 也不会暴露给你——两边都被挡住

打算默认代取。要速度的人可以自己关掉。


怎么开始

  1. 先只测链路(今天就能做,平台这边不用动)

    把几个文件放上去,用手机直接打开那些网址:图开得快不快、语音能不能拖 进度条、换到流量网络还行不行。

    这一半通了再谈后面。不通就没必要往下走。

  2. 按这份文档生成 lyfee.json

    如果你用 AI 助手(Codex / Claude Code),直接把 AGENT_PROMPT.md 整份喂给它,它会把上面这些事 一次做完,包括那几个必须真算的数字。

  3. 等平台这边接上

    接收还没做。做好之后这里会补上「怎么登记你的地址」和「发布了怎么通知 我们」两节。


待定的部分(我们这边还没做)

诚实列出来,免得你照着一份不存在的接口去写:

  • 怎么登记清单地址、verify 校验码从哪里拿
  • 发布之后怎么通知我们(会是「你敲门、我们去取」,不是我们定时轮询—— Vercel Hobby 的定时任务一天只能跑一次,靠轮询的话内容一天才更新一次)
  • 取不到时的显示状态
  • 代取那条路