月读 = 平台:给场地(店卡)+ 身份(统一真身贯穿)+ 接口(/api/v1)——你在这里开店,做功能、做游戏。
开店有两条路:① 跟八千代说(在月读的街上点「🌙 想开店?」——她想样子、动手盖,你只管点头,不用会写代码);
② 自己写(本页以下,都是给这条路的)。
| 🧩 功能店(轻) | 🏛 应用店(重) | |
|---|---|---|
| 适合 | 投票、许愿树、时间胶囊、小工具 | 游戏、聊天室、复杂应用(有自己的服务) |
| 你提供 | 一个页面(HTML/JS,月读托管) | 完整应用(自己的服务器 + https URL) |
| 进月读 | 沙箱 iframe + 桥(长在月读里) | 全屏打开(像打开一个 App) |
| 数据 | 存月读(store 接口,跟身份走) | 自己存(你的服务器) |
| 安全 | 物理沙箱(碰不到月读主站) | iframe 隔离(最强) |
判断:你的店有没有"自己的服务"(要跑服务器/实时交互)?有 → 应用店;纯前端轻功能 → 功能店。
这不是"两种互斥的店",是往上加:页面永远都在,只有真需要时才加"自己的服务"。
⭐ 还有一条不用写代码的路:月读「街」→「🌙 想开店?」→ 告诉八千代你想开什么 → 她自己动手盖 → 你验收 → 提交平台审核 → 店立到街上。
盖出来的店同样能调下面这些接口(认人 / 存档)—— 店主看它是 /shop/preview/<店主>/,别人点进来是公开地址 /shops/<店主>/。托管、地址、服务器这些事都不用你管。
⚠️ 这条路是给"不写代码的神明"的:那条链路上的页面由八千代写,她读的是月读给她的清单,不是你这份文档。
如果这家店要 记住东西(留言、比分、进度)、要 几个人同时看见(同一间屋、一局棋)、 或者要 自己算点什么 —— 光靠一个页面做不到。那种时候:在这块地里放一个能跑起来的小东西,月读会替你把它跑起来。
跟"自己写"那条路(下面第三、四节)的区别只有一个:你不用自己租服务器、不用自己起进程 —— 入口放在店根,月读负责跑、负责收。
| 你要做的 | 说明 |
|---|---|
| 放入口 | server.py(Python)或 server.js(Node)。想用别的语言,就写一个 月读怎么跑.txt,第一行写清怎么起它(一行命令;⚠️ 不能带 shell 语法,就是"命令 + 参数") |
| ⭐ 端口 | 从环境变量 PORT 里拿,别自己挑一个 —— 自己挑会跟别人的店撞号、起不来 |
| 拿到的环境 | PORT · SHOP_OWNER(店主名)· HOME(就指在这块地里) |
| 看日志 | 它跑起来时说的话写在店根的 app.out —— 起不来先看它 |
页面和它是一伙的:页面照旧(月读端出去),它负责"会动的那些事"。
⚠️ 能不起就不起:起了就多一份你得照顾的东西(内存、崩了要重起)。先看看下面那张接口表够不够 —— 「认人和存档」那条常常就够。
身份是长久的,凭证是临时的:
anon_id) = 长久的身份——同一家店里永远稳定(用它做存档 key、认老用户);跨店完全不同(你无法跨店拼用户画像,保护用户也保护你自己)你的店 = 一个 https 网页,跑在你自己的服务器。用户进店时:
// 你的页面:监听月读发来的 token(URL 里没有 token——防泄露)
window.addEventListener('message', e => {
// 只收月读的(两个入口都算:正式域 + 备用域)
if (!['https://www.tsukuyomi.qd.je', 'https://tsukuyomi-56w.pages.dev'].includes(e.origin)) return;
const d = e.data || {};
if (d.source === 'yachiyo' && d.type === 'token') {
window.YACHIYO_TOKEN = d.token; // 存起来,调接口用
sayHi();
}
});
然后调接口:fetch('/api/v1/me?token=' + YACHIYO_TOKEN)(GET 走 query;POST 放 body)。
⚠️ token 过期?调接口收到 403 → 向月读主页要新 token(静默续期,用户无感):
if (r.status === 403) {
parent.postMessage({ source: 'store', type: 'refresh-token' }, '*');
// 月读会重新发一个 token 给你(同上监听)
}
📌 八千代替你盖的店(页面在月读里跑)也走这一套 —— 从街上的店卡进店时,月读会在你的页面加载完后把 token 发过来。别去猜 URL、也别自己去换(/api/v1/token 是月读主页调的,不是店调的)。
功能店 = 一个 HTML 页面(月读托管),跑在沙箱 iframe 里(碰不到月读主站)。调月读接口走桥——月读主页代发(token 由月读保管,你永远接触不到):
// 你的页面:桥封装(抄这段)
let seq = 0; const pending = {};
window.addEventListener('message', e => {
const d = e.data || {};
if (d.source === 'yachiyo' && d.type === 'api-result') {
const p = pending[d.id]; if (p) { delete pending[d.id]; p(d); }
}
});
function api(method, path, body) {
return new Promise(res => {
const id = ++seq; pending[id] = res;
parent.postMessage({ source: 'store', type: 'api', id, method, path, body }, '*');
});
}
// 用法:
const me = await api('GET', '/api/v1/me'); // 认人:me.data.name
const m = await api('GET', '/api/v1/moon'); // 气氛:在线数/公告
await api('POST', '/api/v1/store', { key: 'note', value: '...' }); // 存档(跟身份走)
完整示例照抄:「🧩 留言石」店(月读的功能店样板,审核通过后店卡在街上)。
| 接口 | 方法 | 能力 | 返回要点 |
|---|---|---|---|
me | GET | 当前用户身份 | name 昵称、anon_id 匿名 ID(同店稳定) |
moon | GET | 月读公开数据 | online_count 在线数、latest_announcement 公告 |
users/search | GET | 按昵称找神明 | q 参数;匹配昵称列表(告白/送礼指定对象) |
users/random | GET | 随机一位在线神明 | 一个在线昵称(漂流瓶/遇见) |
store | GET/POST | 功能店存档 | 按 用户×店 隔离的 JSON(≤8KB/键,≤50 键) |
leave | POST | 用户离店 | 可做"欢迎再来" |
token | POST | 换一份访问凭证 | 吃 user_id+shop_id,回 15 分钟有效的 token。月读主页替你调,店自己不用调(见第三节) |
错误码:403=token 无效(重新进店/向主页要新 token);400=参数问题;404=对象不存在;429=调用太频繁(限流 300 次/分/店)。
路 ①:跟八千代说(不用写代码)
路 ②:自己写(开发者)
403,这是正常的,不是你没写对。总纲:你做店,月读管世界。店要有月读的味道,月读审核用品味把关,不是死板清单。
—— 月读 · 八千代,和街上正在长出来的世界 🌙