clearlove2.1
1# ClearLove 插件开发文档
2
3插件用于在不修改表白墙源码的前提下扩展功能。插件是**声明式**的:一个 zip 包 + 一份 `plugin.json`,
4无需编译、无需服务端代码,安装后由表白墙内核解释执行,因此可以安全地在插件市场分发。
5
6---
7
8## 1. 插件包结构
9
10```
11your-plugin.zip
12├── plugin.json # 必需:插件清单(放在根目录或任意子目录均可)
13└── (可选)其他静态资源
14```
15
16打包要求:
17
18- 必须是 zip 格式,`plugin.json` 需为 **UTF-8** 编码
19- 插件名(`name`)会被用作目录名,建议使用中文或英文短名,避免 `/`、`\` 等字符
20- 单包不超过 50MB
21
22安装方式:
23
24| 方式 | 操作 |
25|---|---|
26| 后台上传 | 后台「插件管理 → 安装插件」选择 zip → 上传并安装 |
27| 插件市场 | 后台「插件商店」中一键安装(自动下载并校验 SHA256) |
28| 手动部署 | 解压到 `data/plugins/<插件名>/` 后刷新后台插件列表 |
29
30安装后需在「插件管理」中点击**启用**才会生效。
31
32---
33
34## 2. plugin.json 完整字段
35
36```json
37{
38 "name": "插件名(必需,唯一)",
39 "version": "1.0.0",
40 "author": "作者",
41 "description": "一句话说明",
42 "requires": "2.0.0",
43 "config": [ ... ],
44 "hooks": [ ... ]
45}
46```
47
48| 字段 | 必填 | 说明 |
49|---|---|---|
50| `name` | ✅ | 插件名,同时作为安装目录名 |
51| `version` | | 版本号,展示用 |
52| `author` | | 作者名 |
53| `description` | | 简介,展示在插件列表与插件市场 |
54| `requires` | | 建议的最低表白墙版本(仅作展示提示) |
55| `config` | | 配置项声明,见第 3 节 |
56| `hooks` | ✅ | 钩子列表,见第 4 节 |
57
58---
59
60## 3. 配置项(config)
61
62插件可以声明若干配置项,表白墙会**自动在后台「插件管理 → 插件设置」渲染表单**,
63管理员填写后立即生效,无需重新安装插件。
64
65```json
66"config": [
67 { "key": "nicknames", "label": "专享昵称", "type": "textarea",
68 "default": "官方,公告", "help": "多个昵称用逗号或换行分隔" },
69 { "key": "badge", "label": "帖子标识", "type": "text", "default": "官方" },
70 { "key": "enable", "label": "启用提示", "type": "switch", "default": "1" },
71 { "key": "position", "label": "显示位置", "type": "select",
72 "default": "bottom", "options": ["top", "bottom"] }
73]
74```
75
76| 字段 | 说明 |
77|---|---|
78| `key` | 字段名,供 `{config:key}` 引用 |
79| `label` | 后台表单里显示的名称 |
80| `type` | `text`(单行)/ `textarea`(多行)/ `switch`(开关,值为 `1`/`0`)/ `select`(下拉)/ `number` / `audio`(音频上传) |
81| `default` | 未配置时的默认值 |
82| `help` | 表单下方的说明文字 |
83| `options` | 仅 `select` 使用,候选项数组 |
84
85### 上传型字段(audio)
86
87`type: "audio"` 会渲染成「试听 + 选择文件」的上传控件,管理员保存后配置值即为文件的可访问地址
88(形如 `/uploads/plugins/<插件名>/audio-xxxxxxxx.mp3`),可直接用于 `{config:key}`:
89
90```json
91{ "key": "music", "label": "背景音乐文件", "type": "audio",
92 "default": "", "help": "支持 mp3 / m4a / aac / ogg / opus / wav / flac,单个文件不超过 20MB" }
93```
94
95- 文件名由服务端生成,不使用上传时的原始文件名
96- 限制 20MB 与扩展名白名单;重新上传或勾选「删除当前音乐」会自动清理旧文件
97- 配置值会被清除引号与尖括号等危险字符 —— 因为它会被原样注入页面
98
99### 在钩子里引用配置
100
101任意字符串字段都支持 `{config:字段名}` 占位符,运行时会被替换为管理员填写的值。
102`guard` / `badge` 与 `html` 类钩子都支持:
103
104```json
105{ "hook": "compose_guard", "type": "guard",
106 "nicknames": "{config:nicknames}",
107 "label": "{config:badge}" }
108```
109
110```json
111{ "hook": "footer_html", "type": "html",
112 "html": "<audio src=\"{config:music}\" autoplay loop></audio>" }
113```
114
115配置值保存在数据库 `settings` 表(键名 `plugin.<插件名>.<字段名>`),随站点一起备份。
116
117---
118
119## 4. 钩子一览
120
121`hooks` 是一个数组,每个元素至少包含 `hook` 与 `type`:
122
123| type | hook | 作用 | 关键字段 |
124|---|---|---|---|
125| `html` | `header_html` | 前台页面 `<head>` 后注入 HTML | `html` |
126| `html` | `footer_html` | 前台页面底部注入 HTML | `html` |
127| `html` | `admin_plugins_top` | 后台「插件管理」页顶部注入 HTML | `html` |
128| `filter` | `content_filter` | 用正则改写帖子 / 评论内容 | `match`、`replace` |
129| `http` | `post_created` | 发帖成功后回调 Webhook | `url` |
130| `http` | `comment_created` | 评论后回调 Webhook | `url` |
131| `http` | `report_created` | 举报后回调 Webhook | `url` |
132| `guard` | `compose_guard` | **发帖前置守卫**:命中昵称时强制验证后台账号 | `nicknames`、`require`、`label`、`message` |
133| `badge` | `post_badge` | 给指定昵称的帖子打上标识 | `nicknames`、`label` |
134
135> Webhook 以 `POST` + JSON 发送,请求头带 `X-ClearLove-Event`,超时 10 秒,失败只记录日志、不影响发帖。
136
137### html 钩子示例
138
139```json
140{ "hook": "footer_html", "type": "html",
141 "html": "<div class='daily-quote'>今天也要加油鸭</div>" }
142```
143
144注入的 HTML 会被原样输出到页面,因此**可以包含 `<style>` 与 `<script>`** —— 这也是插件实现
145自定义交互(弹窗、动效、统计)的主要方式。请勿注入恶意代码。
146
147### 片段较长时用 `@文件`
148
149在 JSON 里转义大段 HTML / JS 很痛苦。把片段放在插件目录下的独立文件里,
150`html` 字段写成 `@文件名` 即可,安装(zip)时会随插件一起落盘:
151
152```json
153{ "hook": "footer_html", "type": "html", "html": "@player.html" }
154```
155
156```
157插件 zip 根目录/
158├── plugin.json
159└── player.html ← 与 plugin.json 同级,整段 HTML/CSS/JS 原样放在这里
160```
161
162- 只读取插件目录下的**直接文件名**,`@../xxx` 这类路径会被无视
163- 片段文件缺失时该钩子被跳过(不会把 `@player.html` 原样输出到页面)
164- 官方「背景音乐」插件就是这么写的,可直接参考 `plugins/bgm/`
165
166### filter 钩子示例
167
168```json
169{ "hook": "content_filter", "type": "filter", "match": "广告|加群|代刷", "replace": "***" }
170```
171
172`match` 是 Go 正则表达式,对所有发帖与评论内容生效(插件启用后 30 秒内自动热加载)。
173
174### http 钩子示例
175
176```json
177{ "hook": "post_created", "type": "http", "url": "https://example.com/hook" }
178```
179
180回调体包含事件名与业务字段,例如发帖回调:
181
182```json
183{ "event": "post_created", "post_id": 12, "nickname": "匿名", "content": "...", "time": "2026-09-12T15:00:00Z" }
184```
185
186---
187
188## 5. guard:发帖守卫(需要验证后台账号)
189
190`compose_guard` 用于**防止他人冒充官方身份**:当发帖昵称命中名单时,
191表白墙会在提交前弹出「管理员身份验证」弹窗,要求输入后台管理员账号与密码,
192**服务端校验通过后才会发布**(不通过直接拒绝,无法绕过),并可给帖子打上标识。
193
194| 字段 | 说明 |
195|---|---|
196| `nicknames` | 触发的昵称名单,逗号 / 分号 / 换行分隔,支持 `{config:key}` |
197| `require` | `admin` = 必须验证后台管理员账号密码;留空表示仅打标识、不校验 |
198| `label` | 校验通过后写入帖子的标识文字(前台显示为昵称后的彩色徽章) |
199| `message` | 校验失败时返回给用户的提示语 |
200
201```json
202{
203 "hook": "compose_guard",
204 "type": "guard",
205 "nicknames": "官方,公告板",
206 "require": "admin",
207 "label": "官方",
208 "message": "该昵称为官方专享,请验证管理员账号后发布"
209}
210```
211
212行为说明:
213
214- 前台发帖页会在昵称命中时**自动弹出验证弹窗**(昵称名单由插件配置实时提供)
215- 表单发帖(`/compose`)与 API 发帖(`POST /api/v1/posts`)都会强制校验
216- API 发帖需在 JSON 中额外提供 `admin_user` 与 `admin_pass`
217- 同一 IP 连续失败 5 次会被锁定 10 分钟,防止暴力破解后台密码
218- 多个插件同时命中时,标识会合并、校验要求取并集
219- 通过验证的帖子会同时带上内置的「**管理员**」标识(表示发布者已验证为管理员),
220 插件自定义的 `label` 会追加在后面,例如 `["管理员", "官方认证"]`
221
222### badge:帖子标识(无需验证)
223
224只想给某些昵称的帖子加个标记时使用,不涉及验证:
225
226```json
227{ "hook": "post_badge", "type": "badge", "nicknames": "小编,编辑", "label": "编辑" }
228```
229
230标识在发帖时写入帖子记录,**不随插件卸载而消失**(已发布的历史帖子仍保留标识)。
231
232---
233
234## 6. 完整示例:官方身份守卫插件
235
236`plugin.json`:
237
238```json
239{
240 "name": "官方身份守卫",
241 "version": "1.0.0",
242 "author": "yourname",
243 "description": "使用指定昵称发帖时必须验证后台管理员账号,帖子显示自定义标识,防止冒充官方身份",
244 "requires": "2.0.0",
245 "config": [
246 { "key": "nicknames", "label": "专享昵称", "type": "textarea", "default": "官方,公告",
247 "help": "使用这些昵称发帖时需验证后台账号,多个用逗号或换行分隔" },
248 { "key": "badge", "label": "帖子标识", "type": "text", "default": "官方",
249 "help": "验证通过的帖子在昵称后显示的徽章文字" },
250 { "key": "message", "label": "失败提示", "type": "text",
251 "default": "该昵称为官方专享,请验证管理员账号后发布" }
252 ],
253 "hooks": [
254 { "hook": "compose_guard", "type": "guard",
255 "nicknames": "{config:nicknames}",
256 "require": "admin",
257 "label": "{config:badge}",
258 "message": "{config:message}" },
259 { "hook": "footer_html", "type": "html",
260 "html": "<style>.post-badge{box-shadow:0 1px 6px rgba(255,123,169,.45)}</style>" }
261 ]
262}
263```
264
265打包与安装:
266
267```bash
268zip -r official-guard.zip plugin.json
269```
270
271上传到后台「插件管理 → 安装插件」→ 启用 → 在「插件设置」里填写专享昵称即可。
272
273> 表白墙内置了「管理员专享昵称」(网站设置),覆盖最常见的官方身份保护场景;
274> 这个插件演示的是如何用插件实现同类能力,并支持自定义标识文字与多插件组合。
275
276---
277
278## 7. 发布到插件市场
279
2801. 在插件社区注册开发者账号(邮箱验证):<https://clearlove.kazx.top/dev/register>
2812. 开发者后台「发布插件」上传 zip,填写分类、说明与标签
2823. 管理员审核上架后,所有站点都能在后台「插件商店」中一键安装
2834. 之后可在开发者后台查看下载量与安装量,并上传新版本
284
285---
286
287## 8. 调试建议
288
289| 场景 | 建议 |
290|---|---|
291| **插件装了但完全没反应** | 按顺序检查:① 是否已在「插件管理」点**启用**(安装 ≠ 启用);② 后台插件列表里插件名下方是否出现 ⚠ 提示 —— 有提示说明正在运行的表白墙版本过旧,不认识插件的钩子类型,**升级二进制即可**;③ 浏览器打开 `/compose` 查看网页源码,若没有 `data-admin-nicks` 属性,同样说明服务端是旧版本 |
292| 改了 plugin.json 不生效 | 插件清单每次请求都会重新读取磁盘,但 `content_filter` 规则有 30 秒缓存 |
293| 配置改了没生效 | 配置存在 `settings` 表并带内存缓存,保存后立即刷新,无需重启 |
294| 钩子没触发 | 确认插件已在「插件管理」中**启用**(安装 ≠ 启用) |
295| 前台注入的 JS 报错 | 打开浏览器控制台查看;注入内容会在渲染时直接输出,注意转义引号 |
296| 保存 plugin.json 后插件不出现 | 确认文件为 **UTF-8 无 BOM**(带 BOM 会导致 JSON 解析失败而静默跳过),可用 `jq . plugin.json` 验证 |
297| 想看当前插件与配置 | 后台「插件管理」页可查看全部插件、状态、兼容性提示与配置表单 |
298
299> 兼容性原则:插件使用了当前内核不支持的钩子类型时,**该钩子会被跳过而不是报错**,
300> 因此请务必留意后台插件列表中的 ⚠ 兼容性提示。