一、它解决的问题
linux.do 对纯 AI 生成的水帖是零容忍的,准则里写着:不可以直接使用 AI 生成、润色内容,AI 生成的文字内容只接受截图发出。
麻烦的地方在于判定权不在发帖的人手里。PRD 把痛点写成三种情形:不熟悉《社区准则》、语调误会、无意中使用了类似大模型的句式。三种情形的共同点是,人在按下发布键之前不知道自己踩没踩线。
插件做的事就是把这次判定提前。点按钮之后,它抓走草稿和当前板块,交给模型,返回四个字段:
🟢/🟡/🔴 风险等级
🚫 触犯条款
💡 板块建议
💬 风险说明
判定依据不是模型的常识,而是一段拼出来的 system prompt:社区准则原文被三引号包住塞进去,前面跟一句指令——请严格以此为唯一的判定标尺。
两条原则划定了它不做什么。
只提示,不修改。 理由写在 PRD 里:用 AI 检查一段文字像不像 AI 写的,如果顺手让它改一遍,改完的版本必然更像 AI 写的。这条最后落成 prompt 里的一句约束,「绝对不给改写建议,不帮润色原文」,代码里没有任何机制强制它。
数据不外传。 API Key 和草稿都留在本地。这条落在 manifest 上:权限只要了 storage,主机权限只有 https://linux.do/*,插件没有能力读取任何其他站点。
全部实现是 493 行 JavaScript。
二、六个问题和它们的解法
草稿发不出去。 content script 跑在论坛页面里,它发出的请求来源是 https://linux.do,直接打 AI 接口属于跨站请求。解法是加一个 Service Worker 专门转发:content script 把 URL、请求头和请求体用消息发过去,由它去发请求,来源因此变成 chrome-extension://。这个文件只有 42 行,除了转发不做别的事——一个 20 秒超时,把 JSON 解析失败和 HTTP 状态码错误分成两种错误文案,末尾 return true 保持消息通道打开,否则异步回包会丢。
准则会改。 硬编一份进 prompt,等于让插件按旧规矩审新帖。解法是每次点按钮先看本地缓存,超过 24 小时就从论坛现拉一份:请求 /guidelines,解析出 #main-outlet 的文本,压掉连续空白,截断到 4000 字符存起来。这个请求走相对路径,而 content script 只在 linux.do 注入,所以它是发往论坛自己的同源请求,不需要申请任何额外权限。拉取失败时先回落到旧缓存,旧缓存也没有,才用手抄的那一份。
不知道用户正在往哪个板块发。 板块建议要先知道当前板块,而这个信息在 Discourse 页面里没有固定位置。解法是三级回退:先读发帖框里的板块选择器,取不到就读页面的 og:article:section,再取不到就用正则匹配标题栏结尾的「 - 板块名 - LINUX DO」。三级都落空时,prompt 会改成列出 L 站主要版块、让模型推荐一个。
按钮会被冲掉。 Discourse 是动态渲染的,发帖框随着操作出现和销毁,页面加载时挂一次不管用。解法是用 MutationObserver 监听整个 body,每次 DOM 变动就找一次提交面板,找到就注入,靠按钮类名是否已存在拦住重复注入。这个观察器没有断开逻辑,会跟着页面活到标签页关闭。
配置面板和注入脚本共享不了代码。 配置面板上有个「测试连接」按钮,它要构建的请求和 content script 要构建的是同一套东西——三种协议、各自的请求头和 body 结构。但两者在 Manifest V3 下是隔离的执行环境,不上打包器就没有模块可以共享。解法是各写一份,靠注释维持一致,popup.js 里那行注释写的是「与 content.js 中 buildRequestParams 保持同构」。代价很明确:两处不同步时的症状是测试连接通过、实际使用报错,而没有任何东西会发现这种不一致。
用户会把 https:// 粘两遍。 代码里有一段正则,把开头的 https://https:// 换回 https://,它出现在三个位置:构建请求时、保存配置时、测试连接时。三个入口各修了一次。没有人会凭空写这段补丁,它对应真实发生过的输入错误;至于踩到的是我自己还是某个填中转地址的用户,没有记录,这一条属于推断。
代码在 Ike-li/linux-do-guard-extension,MIT 许可;油猴脚本版在 Greasy Fork。