surface: "service" 的小程序没有界面。它不渲染任何东西,也没有可点的按钮——由站点事件或定时任务唤醒,以自己的账号说话。这是给欢迎新人、自动回答常见问题、定时开话题、协助审核这类事情用的。
它装在哪
普通小程序嵌进一个帖子。机器人装到一个范围上:
节点主在小程序详情页点「安装」,选自己的节点,填好它要的设置即可:


权限就是那个节点版主的权限
装到节点里的机器人,它的账号会加入该节点的版主组。 这一句就是全部的权限模型。之后由 Discourse 核心自己的分类级判断决定它能做什么——它在这个节点里的权限恰好等于这个节点版主的权限,在别的节点里什么都做不了。不是因为哪里写了一条检查,而是因为它不在别人的组里。被什么唤醒
在app.json 的 triggers 里声明:
post_flagged 是协助审核的正规入口:判断「这东西值得看一眼」是成员做的,机器人是被请来帮忙处理,而不是自己四处巡逻。它的载荷区分举报者和被举报者。
说话与处置
优先用
flag.create。 它只要普通权限,因为它的终点是人做决定;删帖要特权,因为它的终点是某人的帖子没了。会打旗标的机器人已经拿到了会删帖的机器人的大部分价值,而且失败方向是安全的。旗标进的是这个节点本来就有的审核队列。
post.delete 不会删主题的首帖——那是从「帖子」这道门把别人整个主题端掉。
限制成员
user.mute 让成员在本节点不能发主题和回复,user.ban 在此之上把他移出节点的用户组。两个都需要 moderate.user 这个单独的特权 scope——对人动手和对帖子动手是两回事,管理员可以只给其中一个。
days 只能是 1、3、7、28 之一,没有「永久」这个选项:机器人出 bug 造成的误伤必须自己愈合,把人永远挡在门外是人做的决定、也由人来负责。被限制的成员会收到站内信,写明期限和理由。
谁动不了: staff、节点主、节点的版主。最后一条包含机器人自己的账号,所以一个应用没法禁言另一个在同节点工作的应用,也没法禁言自己来逃避被关掉。
user.unmute / user.unban 只能解除这个应用自己下的限制。版主亲手下的限制不是应用能撤的,否则它就成了绕过版主的一条路。所有限制都进账本,管理员一次性撤销应用时会被一并解除。
置顶只对机器人自己发的帖子生效,而且只在它装的那个节点里。想说一句会一直留在回复上方的话——版规、长期公告——用 post.reply 的 pin 选项,而不是再声明一条 post.pin:
post.pin 根本写不出它要置顶的帖子 id——那个帖子要等同一批里的 post.reply 跑完才存在。「说这句话,并且让它留在顶部」本来也是一个意思。
已经发出去的帖子仍然可以用 post.pin / post.unpin 按 id 处理。
post.reply 还有两个同样只对自己这条回复生效的选项:lock: true 让这条回复下面不能再回复;collapse: true 让它在嵌套视图里默认收起成一行,读者点一下展开——适合放一条该在但不该挡路的话。三个选项都要 moderate.topic,都进账本,都随一次性撤销恢复。
post.lock 关闭某条回复及其下面所有回复的继续回复,主题的其余部分照常。和置顶正好相反,它不限于机器人自己发的帖子——让别人的争论停下来本来就是它的用途,所以它和删帖同属一类:不能动 staff,不能动本节点的其他版主,不能动主题首帖(那等于从「回复」这道门关掉整个主题)。
锁定同样进账本,管理员一次性撤销时会被解开。
一批 effects 要么全成,要么全不成。所以
pin 被拒(比如全站安装、没有可依托的节点)时,那条回复也不会发出去——不会留下一条本该置顶却躺在回复堆里的公告。读得到什么
这三个都只对本次调用涉及的人作答——谁动的手、谁写的帖、谁被举报。问一个陌生人返回
null,不是报错。
api.kv.app 值得单独说:kv.shared 属于单个安装,这对排行榜是对的,对「已判定账号名单」则毫无用处——那份名单必须在它运行的每个节点里都是同一份。kv.app 就是这个。它不能被枚举,因为真实站点上这块区域是一张几万行的查找表,每有人发帖就整份交出去,等于把它塞进每一次调用的内存里。
访问外部服务
沙箱没有网络,将来也不会有。要访问外部服务,handler 返回一个http.fetch effect——请求由站点代为发出,结果作为一次单独的 onFetch 调用送回来:
- 只允许 https
- 只允许审核给这个小程序逐个批准过的精确主机名(不能用通配符、协议、路径),在
app.json的domains里申请 - 目标地址在连接前会被核对,指向内网的主机名连不出去
- 重定向不跟随——它指向的地址没经过审核
- 响应有大小上限,请求有超时和每日次数上限
API key 之类的凭据放在安装的设置里(由装它的人填),不要写进审核员会读到的代码里。见 app.json 与 readme。
定时干活
只靠定时任务干活的机器人必须导出onInstall:onSchedule 只对已存在的任务触发,而任务只能由 effect 创建——不在 onInstall 里注册第一个 schedule.add,它就永远不会跑起来。
onInstall,所以里面的动作要幂等。schedule.add 按 job_key 覆盖,本身就是幂等的。
开发
post.write 在 playtest 里会被完整校验然后丢弃:能看到它想发什么,但发不出去。