Skip to main content
logo

NoneBot-Plugin-BotUI

python nonebot2 license

给机器人配一个 QQ 风格的 WebUI 控制台:把机器人收发的全部消息记到本地 SQLite,在网页上像翻聊天记录一样查看,并以机器人的身份发言、回复、@ 和撤回。

✨ 功能特性

  • 收发都记:收到的消息由 event_preprocessor 钩子采集,发出的消息由 Bot.on_calling_api 钩子拦截 send_msg 一类的接口记录;图文混合消息按消息段完整保留
  • 引用可读:回复/引用消息会连同被引用者与原文一起保留,气泡上方显示「谁:引用内容」的引用块,点击可跳转到被引用的那条消息
  • 文件接收与在线预览:收到的文件显示为文件卡片(按扩展名着色图标 + 文件名 + 大小),点击即可在弹窗中在线预览 —— 文本类(代码、配置、日志、txt/md/json 等)直接渲染正文,图片、PDF 内联显示,音视频内联播放,其它类型可一键下载;文本预览默认只读取前 512 KB 并提示已截断,下载走真流式代理(支持 HTTP Range 断点续传),默认最大 100 MB(BOTUI_FILE_MAX_BYTES)。文件名会自动还原:像 QQ 官方机器人适配器只给一个下载直链、文件名恒为 file.bin 的情况,插件会从链接的 fname/filename 等参数或路径末段、以及上游响应头里取回真实文件名(含中文),并按真实后缀判断预览方式与下载名
  • 合并转发展开:合并转发消息可在气泡内原地折叠/展开,点开查看每个节点的发送者、时间与内容,优先使用记录里已有的内联节点,缺失时再调用适配器接口拉取
  • 统一媒体库:机器人收到的图片 / 文件 / 语音 / 音视频,以及 WebUI 里上传的附件,都是同一种东西——收到时异步下载入库,发送时正式入库,落在同一个目录、同一张 blobs 表、同一个取回地址 /api/media/<id>。消息段里存的就是这个地址,所以「收到的图链接失效打不开」与「自己发的图过期打不开」从此走同一条保命逻辑。媒体库有配额(默认 512 MB)与保留时间(未被引用的 3 天、被聊天记录引用的 7 天),超限时优先淘汰没人引用的资源;右上角「设置」面板可查看占用并清理(未引用 / 图片 / 文件 / 全部)。开关为 BOTUI_MEDIA_ENABLED
  • 资源按内容(MD5)命名:一个资源的 id 就是它内容的 MD5,落盘为 blobs/<前两位>/<md5>,消息里引用 /api/media/<md5>。内容相同的东西天然只有一份,不必再维护「同一链接只存一次」的去重表;文件名与 MIME 作为展示信息存在数据库里
  • 元数据存数据库而不是散落的 JSON:每个资源一个 meta.json、再配一个全局索引的做法,等于把状态摊在文件系统上——查询要遍历目录、并发要抢文件锁、崩溃会留下半个索引。媒体元数据现在直接建在消息已经用的那条 SQLite 连接上(blobs 表),与消息共享同一个事务与锁;磁盘上只按内容 MD5 落盘
  • 上传附件先走系统临时目录:WebUI 里选中的附件用 tempfile 暂存在系统临时目录(还没发送,不该占配额、也不该被内容去重),前端点「移除」即删;发送那一刻才按内容 MD5 正式进媒体库。因此「选完没发」的附件不会留在磁盘上赖着不走。清理由后台任务按 BOTUI_MEDIA_TTL 兜底,默认每 10 分钟跑一次(BOTUI_MEDIA_CLEANUP_INTERVAL)
  • 聊天记录导出:右键菜单可把当前会话(含时间、发送者与全部消息段)导出为 JSON 文件下载
  • 跨平台:不绑定具体适配器 —— 会话与用户信息来自 nonebot-plugin-uninfo 的 Session,消息解析与发送走 nonebot-plugin-alconna 的 UniMessage / Target
  • 本地存储:消息与会话落盘到插件数据目录的 botui.sqlite3(WAL 模式),媒体元数据存在同一库的 blobs 表里,介质文件放在同目录的 blobs/;支持记录条数上限与保留天数,插件定时自动清理
  • QQ 风格 WebUI:左侧会话列表(搜索、头像、未读小红点、最后一条消息摘要)+ 右侧聊天记录(气泡、日期分隔、图片点击放大)
  • 实时推送:WebUI 通过 /api/ws 建立 WebSocket 长连接(ws:// / wss://),服务端主动推送新消息、撤回与新会话,页面无需轮询;断线按 1s ~ 30s 指数退避自动重连,重连时带上事件序号补发漏掉的那段
  • 新消息网页通知:在右上角「设置」里开启后,收到新消息会弹出浏览器系统通知(可另开提示音,用 WebAudio 合成、无外部音频文件)。为了不打扰,自己发的消息、以及正在看且窗口聚焦的会话都不会提醒,只在该会话未打开、或页面切到后台 / 失焦时弹出;点通知可直接跳到对应会话。再次打开页面会自动校验授权状态,被撤销时会自动关掉
  • 以机器人身份发言:网页上直接发送文本、@ 成员、回复某条消息,并可按配置撤回自己发出的消息;@ 成员从「聊天里出现过的成员名册」中挑选,无需手动输入 QQ 号
  • 发送图片与文件:点输入框旁的 + 选图 / 选文件,也可以直接把文件拖进页面或从剪贴板粘贴(截图直接发)。图片按 Image 段发送、聊天里直接显示,其余按 File 段发送;选中的附件先以缩略图 / 文件名标签排在输入框上方,可逐个移除后再发送。选中的附件先暂存在系统临时目录(移除即删,不占媒体库配额),发送时才按内容 MD5 正式入库;单个附件默认上限 20 MB(BOTUI_MEDIA_FILE_MAX_BYTES)。发出的附件在记录里指向稳定的 /api/media/<md5> 地址,因此聊天记录里自己发的图不会再变成坏图
  • 会话备注:有些适配器拿不到真实的群名或用户昵称(只有一串号码,或名称随时变),可以给会话、成员、机器人分别设置备注。备注优先作为界面上的显示名(原始名称保留在 raw_name 里,会显示在会话头部),参与会话与成员搜索,且不会被适配器后续上报的名称覆盖;只影响展示,不写入聊天记录
  • 多机器人切换:右上角的下拉框会记录所有连接过的机器人,在线的标绿点、离线的置灰;每个机器人的会话、消息与搜索结果彼此隔离,切换后互不串台。给某个会话发消息时只会用它自己的机器人发送,该机器人不在线时宁可失败也不会冒用别的机器人
  • 令牌鉴权 + 仅本机访问:默认开启,访问令牌自动生成并保存在数据目录,默认只允许本机来源的请求
  • 零额外端口:路由直接挂到宿主项目已有的 FastAPI 应用上(nonebot2[fastapi]),不新起服务
  • 不注册任何指令:不占用命令名、不拦截聊天消息,访问地址只在启动时打印到控制台日志

📦 安装

nb plugin install nonebot-plugin-botui

或用 pip 安装:

pip install nonebot-plugin-botui
# 尚未发布到 PyPI 时,也可以直接从仓库安装:
pip install git+https://github.com/Lonely-Sails/nonebot-plugin-botui.git

安装后在 pyproject.toml 的 [tool.nonebot] 中加入:

plugins = ["nonebot_plugin_botui"]

WebUI 依赖宿主项目的 FastAPI 驱动:请安装 nonebot2[fastapi](并在 .env 里配置 DRIVER=~fastapi),否则插件启动时会提示「未能拿到 FastAPI 应用」。

依赖:nonebot2 >= 2.3.0、nonebot-plugin-alconna、nonebot-plugin-uninfo、nonebot-plugin-localstore、nonebot-plugin-apscheduler、fastapi、aiosqlite、httpx。

🚀 快速开始

插件不注册任何聊天指令,不会占用命令名,也不会拦截用户消息。启动机器人后直接看控制台日志:

  1. 启动日志里会给出带令牌的访问地址,以及数据存放位置:

    [INFO] nonebot_plugin_botui | http://127.0.0.1:8080/botui/?token=xxxxxxxx
    [INFO] nonebot_plugin_botui | 仅本机可访问;需要远程访问请设置 BOTUI_ALLOW_REMOTE=true
    [INFO] nonebot_plugin_botui | 数据:/path/to/data/botui.sqlite3
    

    每行都是一条独立日志(前缀由 NoneBot 的日志格式补上)。只有偏离默认值的开关才会额外列出(例如关掉了采集、关掉了鉴权);保持默认时不会啰嗦。

  2. 打开那个链接即可。前端会自己把地址栏里的 ?token= 抹掉,并保存到浏览器 localStorage,下次直接访问 /botui/ 就行;换浏览器时在令牌输入框里粘贴令牌即可。

几个说明:

  • 日志若显示未能挂载 WebUI,通常是驱动不对:WebUI 依赖宿主项目的 FastAPI 驱动,请安装 nonebot2[fastapi] 并在 .env 里配置 DRIVER=~fastapi。
  • 令牌默认自动生成并写入插件数据目录下的 token.txt(目录由 nonebot-plugin-localstore 提供;文件权限为 0600)。想固定令牌就设置 BOTUI_TOKEN。
  • 关闭令牌后地址里不会带 ?token=,此时任何能访问该地址的人都能查看记录并冒充机器人发言,启动日志会明确警告。

⚙️ 配置

在 .env / .env.prod 中配置(环境变量前缀 BOTUI_,完整示例见 .env.example):

配置项 类型 默认值 说明
BOTUI_ENABLED bool true 是否启用 WebUI;关闭后插件不采集也不挂载
BOTUI_ROUTE str /botui WebUI 挂载路径,不以 / 开头会自动补上
BOTUI_HOST str 127.0.0.1 提示链接里使用的主机名(只影响链接显示,不改变安全策略)
BOTUI_ALLOW_REMOTE bool false 是否允许非本机来源访问;开启前请确认令牌鉴权已启用(见「安全提示」)
BOTUI_ALLOWED_HOSTS tuple[str, ...] () 允许的 Host 头白名单,留空则不校验;走域名(反向代理)访问时填上域名即启用,用于挡 DNS rebinding
BOTUI_AUTH bool true 是否启用令牌鉴权(强烈建议保持开启)
BOTUI_TOKEN str 空 访问令牌;留空则自动生成并保存到数据目录的 token.txt
BOTUI_CAPTURE_RECEIVED bool true 是否记录机器人收到的消息
BOTUI_CAPTURE_SENT bool true 是否记录机器人发出的消息
BOTUI_CAPTURE_SELF bool false 是否记录「自己发出的消息被适配器回传成事件」的情况;关闭可避免同一条消息被记两次
BOTUI_EXCLUDE_ADAPTERS tuple[str, ...] () 不记录这些适配器的消息,例如 ["OneBot V12"]
BOTUI_MAX_RECORDS int 200000 消息记录上限,超出后自动删除最旧的记录;0 表示不限制
BOTUI_RETENTION_DAYS int 0 消息保留天数,超期自动清理;0 表示不限制
BOTUI_RESOLVE_AT_NAME bool true 是否把 @某人 解析成群名片(会调用适配器接口并缓存)
BOTUI_API_TIMEOUT float 10.0 插件内部调用适配器接口(如获取群成员信息)的超时时间(秒)
BOTUI_PAGE_SIZE int 50 聊天记录每页条数,取值范围 10 ~ 200
BOTUI_WRITE_ENABLED bool true 是否允许在 WebUI 中代替机器人发送消息;false 时网页进入只读模式
BOTUI_ALLOW_RECALL bool true 是否允许在 WebUI 中撤回消息
BOTUI_MEDIA_ALLOW_PRIVATE bool false 图片代理是否允许访问内网/环回地址;除非确实要用它代理内网图床,否则保持关闭(见「安全提示」)
BOTUI_FILE_PREVIEW bool true 是否允许在网页上在线预览收到的文件(文本 / 图片 / PDF / 音视频)
BOTUI_FILE_MAX_BYTES int 104857600 文件下载代理允许的最大体积(字节,默认 100 MB);0 表示不限制
BOTUI_MEDIA_ENABLED bool true 是否启用媒体库(下载收到的媒体、接收上传的附件);关闭后链接只能实时代理,失效即打不开
BOTUI_MEDIA_MAX_BYTES int 536870912 媒体库占用上限(字节,默认 512 MB),超出后自动淘汰最久未用的资源;0 表示不按体积限制
BOTUI_MEDIA_MAX_FILES int 20000 媒体库文件数量上限;0 表示不按数量限制(资源按内容去重,条数通常远小于消息数)
BOTUI_MEDIA_FILE_MAX_BYTES int 20971520 单个资源进入媒体库的最大体积(字节,默认 20 MB),超过的只代理不落盘;0 表示不限制
BOTUI_MEDIA_TTL float 259200.0 未被聊天记录引用的资源保留时长(秒,默认 3 天),到期自动回收;0 表示不按时间清理
BOTUI_MEDIA_RETENTION float 604800.0 已被聊天记录引用的资源保留时长(秒,默认 7 天);0 表示不限制
BOTUI_MEDIA_FETCH_TIMEOUT float 20.0 下载单个媒体资源入库的超时时间(秒)
BOTUI_MEDIA_MAX_BYTES_PER_MESSAGE int 20971520 单条消息最多下载多少字节入库(默认 20 MB),避免一条消息把媒体库刷满;0 表示不限制
BOTUI_MEDIA_CLEANUP_INTERVAL int 10 媒体库自动清理的间隔(分钟);0 表示只保留每 6 小时一次的消息记录清理
BOTUI_SEND_INTERVAL float 1.0 同一会话两次发送之间的最小间隔(秒),用于限流
BOTUI_DEBUG bool false 打印更详细的调试日志

数据存放位置

数据库与令牌的路径完全交给 nonebot-plugin-localstore,插件只读取它给出的目录,不再提供自己的目录配置项:

# 全局改(所有插件生效)
LOCALSTORE_DATA_DIR=/srv/nonebot/data

# 只改本插件(推荐,用插件 ID 作 key)
LOCALSTORE_PLUGIN_DATA_DIR={"nonebot_plugin_botui": "/srv/botui/data"}

默认位置参考 localstore 的规则:LOCALSTORE_USE_CWD=true 时为 <运行目录>/data/nonebot_plugin_botui/,否则为系统数据目录下的 nonebot2/nonebot_plugin_botui/。可以用 nb localstore 查看实际路径。目录下会生成:

  • botui.sqlite3:消息、会话与媒体元数据(blobs 表)数据库(WAL 模式)
  • token.txt:自动生成的访问令牌(权限 0600)
  • blobs/:媒体库的正文 —— 收到的图片/文件、已发出的附件都在这里,布局为 blobs/<md5前两位>/<md5>(正文名就是内容的 MD5)。整目录可删:删掉只是丢掉本地副本,不影响聊天记录本身,下一次访问会重新去上游链接取(前提是链接还没失效)

路径在插件导入时解析一次并存为常量。localstore 是靠调用栈反查「哪个插件在要目录」的,只有栈上存在插件自己的帧时才能查到(从 __main__、别名模块名或外部库回调里现取会报 Cannot detect caller plugin)。导入时解析最稳妥,之后全项目只读常量。

🎉 命令

没有命令。 插件不注册任何 matcher,不 require 任何前缀,也不拦截聊天消息 —— 访问地址只在启动时打印到控制台日志(见「快速开始」)。

这样设计是为了避免和你的其他插件抢命令名:早期的版本注册过 帮助 / 状态 / 链接 / 统计 / 清理 / 重置 这些又短又通用的别名,很容易和别人撞车。

清理旧记录不需要手动触发:插件每 6 小时会按 BOTUI_MAX_RECORDS 与 BOTUI_RETENTION_DAYS 自动执行一次。

🖥️ 界面说明

  • 主题:默认深色,右上角按钮可切换浅色,选择保存在 localStorage(botui_theme)。
  • 机器人切换:右上角下拉框列出所有连接过的机器人(在线的标绿点、离线的置灰,显示头像、名称、self_id 与适配器)。始终恰好选中一个机器人:进来时优先选中上次选过的(记录在 localStorage 的 botui_bot),否则选中第一个在线的。切换机器人会清空当前会话与消息列表,只显示该机器人的会话与记录;连接状态由服务端推送实时更新,选择的机器人离线时会自动挑选下一个在线的。
  • 设置面板:右上角齿轮打开,包含两部分。新消息通知 —— 开关网页通知、提示音;开启时会请求浏览器授权,被拒绝或浏览器不支持时开关置灰并显示原因(偏好记在 localStorage 的 botui_notify)。本地缓存 —— 显示占用、资源数、图片/文件分类占用与本进程的命中统计,并提供「刷新 / 清理未引用 / 清理图片 / 清理文件 / 清空媒体库」几个操作。清空需要连点两次确认(第一次点会变成「确认清空?」)。媒体库未开启时缓存区域的按钮全部置灰。
  • 会话记忆:每个机器人最近打开的会话会记在 localStorage(botui_chat,形如 {self_id: chat_key}),下次打开页面或切回该机器人时自动打开那个会话;若已不存在(被清理或换了机器人)则照常停留在会话列表。
  • 备注(别名):适配器拿不到真实群名或昵称时,可以自己给它们起个认得出的名字。会话:会话头部会话名旁的铅笔按钮,或在左侧会话列表上右键 → 设置备注;设置后会话头部会以备注为名、下方显示「原名 <适配器给的名称>」。成员:在输入框 @ 菜单里右键某个成员,或在聊天记录里右键某条消息 → 备注发言人。机器人:右上角机器人切换器菜单里每行右侧的铅笔按钮。备注优先作为显示名,参与搜索,且不会被适配器上报的名称覆盖;清空后即恢复显示适配器给的名称。
  • 令牌页:开启鉴权时,若浏览器里没有令牌(或令牌失效返回 401),会先弹出输入框;令牌只存在当前浏览器的 localStorage 中,不会回传服务器保存。
  • 会话列表:按最后消息时间倒序,显示头像(拉不到图就用名称首字生成彩色圆)、名称、最后一条消息摘要(机器人发出的以「我: 」开头)、时间(今天为 HH:MM,昨天为「昨天」,更早为 MM-DD / YYYY-MM-DD)与未读数(超过 99 显示 99+,未打开的会话显示「新」+ 摘要)。
  • 搜索:顶部搜索框输入后出现「会话 / 消息」两个标签。会话标签即时过滤会话名、会话 ID(群号/QQ 号)与最后一条消息;消息标签会请求 /api/search 做正文全文检索,结果按时间倒序展示并高亮关键词,点一条即可跳到对应会话并定位到该消息(不在已加载的一页时会提示)。
  • 聊天记录:左右分栏气泡,连续消息紧凑排版,按天插入「今天 / 昨天 / 5月1日 / 2024年5月1日」分隔;切换会话时先显示骨架占位;图片消息点击放大(灯箱,点击任意处或按 Esc 关闭),@、回复、表情、文件、语音、视频、卡片、合并转发等消息段有对应样式;向上滚动自动加载更早的消息,单次会话最多在前端保留 1200 条。
  • 文件与预览:文件消息显示为文件卡片,点击在弹窗内预览 —— 文本类(代码/日志/配置等)直接显示正文并在超过 512 KB 时提示截断,图片与 PDF 内联显示,音视频提供内联播放器,其它类型给出下载按钮(弹窗右上角也可下载/另存)。语音、音频、视频消息也都有内联播放器;图片与媒体统一走 /api/media/<id>(媒体库副本),文件走 /api/file 流式代理(带令牌与 SSRF 校验)。若资源已在媒体库里(响应头带 X-BotUI-Cache: hit),/api/file、/api/preview 会直接读本地副本,不再联网。预览开关为 BOTUI_FILE_PREVIEW。
  • 合并转发:合并转发消息显示为可折叠的「聊天记录」卡片,默认收起,点击标题栏原地展开成一条条小聊天记录(不弹窗),逐条显示发送者昵称、时间与内容。节点优先取自记录里的内联数据,只有在缺内联数据时才向适配器请求,且只在首次展开时拉取。
  • 实时刷新:页面通过 WebSocket(/api/ws)接收服务端推送的事件,新消息实时置顶并提示「N 条新消息」,新会话自动进入列表,撤回事件会把气泡移除。连接失败/断开时按 1s ~ 30s 指数退避重连,并在左下角显示连接状态(令牌无效用 4401、来源被拒用 4403,分别对应弹令牌框 / 提示只允许本机);重连会带上最后收到的事件序号,服务端从环形缓冲里补发漏掉的那段;序号出现较大空洞或长时间没收到任何推送时会自动重新同步一次。不在底部时新消息不打扰阅读,只提示「N 条新消息」。
  • 发送 / 回复 / @:底部输入框 Enter 发送、Shift+Enter 换行;@ 按钮打开成员选择菜单(关键词过滤、显示头像与群主/管理员标签),只能从「这个群里记录到的成员」里选,不再要求手输 QQ 号,选中的成员以「@昵称」标签形式展示、可逐个移除;右键消息选「回复」会在输入框上方显示引用条(含被引用者与原文摘要),发送时带上回复段。
  • 发送图片 / 文件:输入框左侧的 + 按钮打开文件选择框(可多选),也可以把文件拖进页面任意位置(会显示拖拽提示层),或在输入框里直接粘贴剪贴板里的图片(截图 → Ctrl/Cmd+V → 发送)。选中的附件会先上传到服务端并以缩略图(图片)或「类别 + 文件名 + 大小」标签排在输入框上方,可逐个 ✕ 移除。发送时可以只发附件不发文字。上传进度反映在发送按钮上(显示「上传中」),上传中的附件不能发送。
  • 右键菜单:复制文本、回复、备注发言人(给该消息的发送者设置备注,仅收到的消息可用)、撤回(仅机器人自己发出且拿到消息 ID 的消息可用,且需要 BOTUI_ALLOW_RECALL=true;不支持撤回的适配器会在调用时返回失败原因)、预览文件(文件消息可用)、导出聊天记录(导出当前会话为 JSON)、复制消息ID。左侧会话列表上右键则是 设置备注。
  • 只读模式:BOTUI_WRITE_ENABLED=false 时输入框、发送按钮、@ 按钮与 + 附件按钮均被禁用,底部提示「只读模式」;备注编辑同样会被拒绝(返回 403)。
  • 窄屏:宽度小于 768px 时变成单栏,点消息头部返回按钮回到会话列表;输入框字号会自动提到 16px,避免 iOS 聚焦时把整页放大;同时按 viewport-fit=cover 处理了刘海屏安全区。

🔐 安全提示

  • 默认开启令牌鉴权(BOTUI_AUTH=true)。令牌要么由 BOTUI_TOKEN 指定,要么自动生成 24 字节随机串写入数据目录的 token.txt(权限 0600)。请勿把令牌或 token.txt 泄露出去。
  • 令牌优先走请求头。普通接口一律用 X-BotUI-Token;地址栏里的 ?token= 只对浏览器无法带自定义头的路径放行(/media、/api/media、/api/file、/api/preview、/api/export 与 WebSocket /api/ws),写接口一律不接受它 —— 这样令牌不容易被反向代理访问日志、浏览器历史或第三方子资源请求带出去。鉴权失败次数过多会短时返回 429。
  • 默认只允许本机访问。服务端会拒绝非本机来源的请求(返回 403)。判断来源用的是 ipaddress 的 loopback 判定,所以 127.0.0.1、127.0.0.2、::1、::ffff:127.0.0.1、localhost 都算本机。要开放给外部,需要显式设置 BOTUI_ALLOW_REMOTE=true。
  • Host 头白名单(挡 DNS rebinding,按需启用)。BOTUI_ALLOWED_HOSTS 留空时不做任何校验(默认),这样用主机名访问本机(如 http://my-nas:8080)不会被打回。走域名(反向代理)部署时把域名写进去即启用白名单:本机主机名(localhost、127.0.0.1、[::1])始终允许,其余 Host 一律拒绝。这样即便攻击者用「先解析到自己的服务器、再改解析到 127.0.0.1」的手法让来源变成环回,也会因为 Host 不对而被拒。
  • 响应头加固。BotUI 路由下的响应都会带上 X-Content-Type-Options: nosniff、X-Frame-Options: DENY、Referrer-Policy: no-referrer 与 frame-ancestors 'none' 的 CSP。nosniff 尤其重要:/api/file 会按上游给的 content-type 内联返回文件,没有它时浏览器可能对内容做嗅探。此外 /api/meta 会返回 robots_tag,前端据此插入 <meta name="robots" content="noindex, nofollow">,避免被搜索引擎收录。
  • BOTUI_HOST 只管链接显示。它决定启动日志里给出的主机名(比如你走反向代理,就填域名),不会改变来源限制。这样把链接改成局域网地址或域名都不会意外放开访问;反过来也不会因为填了域名就把自己锁在外面。
  • 若同时开启 BOTUI_ALLOW_REMOTE=true 与 BOTUI_AUTH=false,启动时会打印一条醒目警告 —— 这是「任何人只要能访问该地址就能查看全部记录并冒充机器人」的状态。
  • 链接里带的是单一密钥。?token=xxx 就是全部凭据:谁能拿到链接,谁就能查看全部聊天记录并以机器人的身份发消息、撤回消息。地址只出现在服务端控制台日志里(不会发到任何聊天窗口),请自行保管好日志输出。
  • BOTUI_AUTH=false 意味着完全开放(在来源限制之内):任何能访问该地址的人都能查看记录、以机器人身份发言和撤回;启动日志里也会明确提示「令牌鉴权已关闭」。
  • 图片代理:/media 需要令牌,且只代理公网地址。它会把目标域名解析一遍,只要解析结果里有环回、内网、链路本地(含 169.254.169.254 这类云元数据地址)等非公网地址就拒绝,重定向每一跳都会重新校验,因此不能拿它当跳板去读内网服务。如果你的图床确实在内网,才需要打开 BOTUI_MEDIA_ALLOW_PRIVATE=true。
  • 媒体库落在磁盘上:收到的媒体会被下载、上传的附件会直接存入 localstore 的插件数据目录下的 blobs/,所以磁盘占用会随收发的媒体增长。除按 BOTUI_MEDIA_MAX_BYTES / BOTUI_MEDIA_MAX_FILES 自动淘汰外,还可在 WebUI 的「设置」面板手动清理。若不想让它落盘,设 BOTUI_MEDIA_ENABLED=false 即可(此时仍可代理,只是链接失效后就打不开了)。
  • 文件代理:/api/file(流式下载/预览)与 /api/preview(文本预览)同样需要令牌,并复用与图片代理一致的 SSRF 校验与重定向逐跳校验。下载默认限制 100 MB(BOTUI_FILE_MAX_BYTES,0 表示不限制),文本预览最多读取前 512 KB。若你的文件确实在内网,同样需要 BOTUI_MEDIA_ALLOW_PRIVATE=true。
  • 附件上传:POST /api/upload(暂存到系统临时目录)需要令牌且仅在可写模式下可用(BOTUI_WRITE_ENABLED=false 时直接 403),取回/删除时 id 经严格校验,只允许本插件生成的 id,../ 之类的路径穿越会被拒绝;文件名会剥离路径与控制字符后才展示。单个附件默认上限 20 MB(BOTUI_MEDIA_FILE_MAX_BYTES)。已发送的附件在发送时按内容 MD5 正式进媒体库(未发送的暂存文件随进程结束、移除或超时被删掉),因此请确保磁盘空间与访问权限可控;BOTUI_MEDIA_ENABLED=false 可以整体关闭该功能。发出的附件与收到的媒体存在同一份库、同一个 /api/media/<md5> 地址下。
  • 需要公网访问时:建议放在反向代理之后,加 HTTPS 与额外的访问控制,并保持令牌鉴权开启;连同 BOTUI_WRITE_ENABLED=false 一起使用还可以只读地公开记录。
    • 注意反代与机器人在同一台机器上时,请求来源就是 127.0.0.1,来源限制会自然放行 —— 此时鉴权完全依赖令牌,请务必保持 BOTUI_AUTH=true 并确认反代不会把 ?token= 记进日志。
    • 若反代在另一台机器上,才需要 BOTUI_ALLOW_REMOTE=true。

🌐 跨平台说明

  • 用户与场景信息统一由 nonebot-plugin-uninfo 的 Session 提供,发送目标由 nonebot-plugin-alconna 的 Target 构造,因此插件本身不绑定任何适配器;supported_adapters 直接继承自这两个插件。
  • 多机器人:每个机器人(self_id)的会话彼此独立 —— 会话 key 采用 self_id:kind_id 的形式内嵌机器人 ID,同名群在 A、B 两个机器人下就是两条不同的会话;每条消息都带上产生它的适配器名与 self_id。WebUI 的发送只会用会话自己的机器人,它不在线时返回 503 而不会改用别的机器人。
  • 只要适配器支持对应的能力就能用:私聊/群聊识别、消息段解析、以及通过 alconna 的 exporter 发送消息。召回(撤回)需要该适配器在 alconna 里实现了对应的 exporter,不支持时网页会提示「适配器 xxx 不支持撤回」。
  • BOTUI_EXCLUDE_ADAPTERS 可以按适配器名(如 ["OneBot V12"])排除某些平台的记录。
  • QQ 表情:官方 QQ 适配器的 emoji 段只给一个编号(data.id,即 QQNT 的 emojiId),网页按这个编号显示表情图并带上中文名(如 67 → 心碎、179 → doge),图缺失时退回「[中文名]」文字标签。表情图与名称表随插件分发在 static/face/(327 张 64×64 WebP,约 600 KB + 运行时惰性加载的 names.json),素材取自 QFace(MIT),按 QQNT 索引同步,与旧 QZone 编号不通用。

🧪 开发 / 测试

uv sync                  # 安装依赖
uv run poe test          # 跑测试(pytest + 覆盖率,产出 coverage.xml / junit.xml)
uv run ruff check .      # lint
uv run ruff format .     # 格式化
uv run poe bump patch    # 版本号变更(bump-my-version)

📄 协议

MIT © 2026 Lonely-Sails

内置的 QQ 表情素材(static/face/)来自 QFace,遵循其 MIT 协议;表情形象版权归腾讯所有,仅用于本地展示消息中的表情。

Metadata

Release files for nonebot-plugin-botui 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nonebot-plugin-botui 0.1.1
File Size Uploaded
nonebot_plugin_botui-0.1.1.tar.gz 785.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nonebot-plugin-botui 0.1.1
File Interpreter ABI Platform
nonebot_plugin_botui-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / nonebot_plugin_botui-0.1.1.tar.gz

Download URL nonebot_plugin_botui-0.1.1.tar.gz
Size 785.1 kB
Tags Source
SHA-256 checksum
How to use checksums
17141093c1cb4960afedff43acaf98aeca1ef0966e6860d045655e8b23880953
BLAKE2b-256 checksum
How to use checksums
ac791ec60dfca511f21d599a278e5b01fec4f3e2d1a132696c0327a7e6289685
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / nonebot_plugin_botui-0.1.1-py3-none-any.whl

Download URL nonebot_plugin_botui-0.1.1-py3-none-any.whl
Size 862.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ed1e073eb4d2ce9fb619541e671720810b4c2cbe0d501aa4d56f4f9fab7eb053
BLAKE2b-256 checksum
How to use checksums
4c520d1e0bdc1ef0bc5692bc469bb9e37cc3deeee01a6d12fba3a5921e0cf99b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page