news 2026/9/12 17:05:24

Buzz 非 AI 机器人开发实战:基于 Countdown Bot 示例打通 NIP-42 认证与频道消息接入

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Buzz 非 AI 机器人开发实战:基于 Countdown Bot 示例打通 NIP-42 认证与频道消息接入

Buzz 非 AI 机器人开发实战:基于 Countdown Bot 示例打通 NIP-42 认证与频道消息接入

【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz

本指南以开源仓库 buzz 中 examples/countdown-bot 示例为骨架,讲解如何在 Buzz(基于 Nostr 协议的蜂巢式通信平台)上构建一个不需要任何 AI/LLM 能力的纯算法机器人。通过阅读本文,你将掌握 Buzz 参与者的最小协议要求、两种中继认证路径(独立身份与 NIP-OA 所有者背书)、频道自加入机制,以及如何用一条命令在本地把机器人跑起来。

示例概览:一个"故意无聊"的算法机器人

Countdown Bot 是仓库中的一个刻意保持简单、不依赖任何模型推理的机器人示例。它只做一件事:监听一个 Buzz 频道,并对简单命令给出确定性回复

输入输出
!countdown 55 4 3 2 1 🚀
!fib 813 8 5 3 2 1 1 0
@Countdown Bot fib 813 8 5 3 2 1 1 0

这段行为在 examples/countdown-bot/src/main.rs 的单元测试中有明确固化,例如command_reply("!countdown 5")必须等于"5 4 3 2 1 🚀"command_reply("!fib 101")必须返回越界提示"Please use a number from 1 to 100."

它存在的意义不是功能本身,而是证明一条关键结论:Buzz 的参与者不必是 LLM Agent。任何能够持有 Nostr 密钥、应答 NIP-42 认证、发布 kind0个人资料、订阅事件并发布 kind9频道消息的进程,都可以成为 Buzz 机器人。

成为 Buzz 机器人的最小协议要求

根据 README 的界定,一个进程要成为 Buzz 参与者,只需满足以下协议能力:

  1. 持有 Nostr 密钥:拥有nsec或十六进制私钥,用于给事件签名;
  2. 应答 NIP-42 AUTH 挑战:在 WebSocket 握手后完成中继的认证握手;
  3. 发布 kind0个人资料:让机器人拥有可识别的名称、头像与简介;
  4. 订阅频道事件:通过 REQ 订阅目标频道下的 kind9消息;
  5. 发布 kind9频道消息:以频道成员身份向频道写入回复。

这五项能力对应了 main.rs 中主流程的四个阶段:

let mut ws = connect_and_authenticate(&config).await?; publish_profile(&mut ws, &config).await?; announce_channel_membership(&mut ws, &config).await?; subscribe_to_channel(&mut ws, &config.channel_id).await?;

随后进入一个tokio::select!事件循环,一边处理Ctrl-C信号优雅退出,一边消费中继推送的文本消息。示例刻意选择了"直接 WebSocket + NIP-42"而非 MCP 通道,目的正如 README 所注:让整条协议路径可以在单个小文件中完整审查,不被 SDK 的抽象层掩盖。

启动阶段的源码解析

1. 发布 kind0个人资料

机器人启动后首先发布一份名为Countdown Bot的 kind0资料,其中包含一个内嵌的 SVG 时钟图标(以data:image/svg+xmlData URL 形式放入picture字段)。源码通过buzz-sdk的 build_profile 构造事件:

let builder = buzz_sdk::builders::build_profile( Some(BOT_DISPLAY_NAME), // "Countdown Bot" Some(BOT_NAME), // "countdown-bot" Some(BOT_ICON_DATA_URL), // 内嵌 SVG 时钟图标 Some(BOT_ABOUT), // 机器人简介 None, // nip05 )?;

build_profile只把传入的Some字段写入 JSON 对象,因此机器人可以按需只暴露display_namenamepictureabout中的任意组合。

2. 以 kind:9000 自加入频道(成员身份公告)

接着机器人以"尽力而为"(best-effort)的方式发布一个 NIP-29kind:9000自加入事件,携带role=bot标签。对应源码 announce_channel_membership:

let builder = EventBuilder::new(Kind::Custom(9000), "").tags([ Tag::parse(["h", config.channel_id.as_str()])?, Tag::parse(["p", &config.bot_keys.public_key().to_hex()])?, Tag::parse(["role", "bot"])?, ]);

这里的三类标签与 SDK 中 build_add_member(NIP-29 add-member)的约定一致:h指向频道 UUID,p指向被加入成员的公钥,role声明成员角色。正是这则成员事件让机器人出现在频道成员列表、并进入 Buzz 的 @ 提及自动补全中。如果自加入失败(例如私有频道没有写入权限),代码只打印告警而不中断运行——README 明确提示:私有频道需要由 owner/admin 先把机器人公钥加入频道成员,它才能被提及、读取和写入。

3. 订阅频道 kind9消息

订阅过滤器只关注 kind9h标签等于目标频道 UUID 的事件:

let filter = Filter::new().kind(Kind::Custom(9)).custom_tag( SingleLetterTag::lowercase(Alphabet::H), channel_id.to_string(), );

这对应了 README 中"发布 kind9频道消息"这一核心交互面——Buzz 的频道消息正是通过带h标签的 kind9事件承载的。

4. 消息处理与防回环

收到事件后,maybe_reply 先做两道过滤:

  • 忽略自己的消息event.pubkey == config.bot_keys.public_key()时直接返回,避免机器人回复自己的回复形成死循环;
  • 忽略历史事件event.created_at < started_at(启动时刻)之前的事件不处理,避免订阅回放触发旧消息批量回复。

通过过滤后,机器人用buzz_sdk::builders::build_message构造 kind9回复,并把被回复者的 pubkey 作为 p 标签提及传入(详见下文"提及命令"一节)。

认证路径一:独立机器人身份(standalone)

当机器人应当以自己独立的中继身份被接纳时,使用standalone模式。此时机器人只用自己的密钥完成 NIP-42 认证,不借助任何其他身份:

BUZZ_RELAY_URL=ws://localhost:3000 \ BUZZ_CHANNEL_ID=<channel-uuid> \ BUZZ_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \ BUZZ_BOT_AUTH_MODE=standalone \ cargo run --manifest-path examples/countdown-bot/Cargo.toml

关闭(closed)或白名单(allowlisted)中继上,需要先把机器人公钥加入中继成员,或加入配置的公钥白名单,再启动机器人。这条路径不复用任何所有者的访问权限——吊销机器人即从成员中移除该机器人公钥

对应源码中,Config::from_env(main.rs)在auth_mode == "standalone"时将owner_auth_tag置为None,认证事件退化为标准的 NIP-42 AUTH:

EventBuilder::auth(challenge, relay_url).sign_with_keys(&config.bot_keys)?

认证路径二:所有者背书机器人身份(owner-attested)

owner-attested模式下,机器人仍然用自己密钥签名消息,但它的 NIP-42AUTH事件额外携带一个由所有者密钥签名的 NIP-OAauth标签。这一路径复用了 Buzz Agent 在 owner/agent OAuth 流程后获得的同一套"所有者背书"凭证机制:中继允许机器人连接,是因为它的所有者是中继成员,而不必把机器人密钥做成持久中继成员

方式 A:启动时动态生成 auth 标签

BUZZ_RELAY_URL=ws://localhost:3000 \ BUZZ_CHANNEL_ID=<channel-uuid> \ BUZZ_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \ BUZZ_OWNER_PRIVATE_KEY=<owner-or-agent-nsec-or-hex-secret> \ BUZZ_BOT_AUTH_MODE=owner-attested \ cargo run --manifest-path examples/countdown-bot/Cargo.toml

配置解析时,若未提供BUZZ_AUTH_TAG,代码会调用 SDK 的 compute_auth_tag 现场生成背书:

buzz_sdk::nip_oa::compute_auth_tag(&owner_keys, &bot_keys.public_key(), "")?

然后立即用 verify_auth_tag 自检,确认标签与机器人公钥匹配后打印背书所有者,再交给 parse_auth_tag 解析为可嵌入 AUTH 事件的Tag(该解析只做结构校验、不做密码学运算,是 MCP 启动等快速路径使用的入口)。

方式 B:预计算并显式传入标签

也可以在外部预先算好标签,直接注入环境变量:

BUZZ_AUTH_TAG='["auth","<owner-pubkey>","","<sig>"]' \ BUZZ_BOT_AUTH_MODE=owner-attested \ # plus BUZZ_RELAY_URL, BUZZ_CHANNEL_ID, BUZZ_BOT_PRIVATE_KEY cargo run --manifest-path examples/countdown-bot/Cargo.toml

预计算可以使用 SDK 提供的独立示例命令 compute_auth_tag:

cargo run --release --example compute_auth_tag -- <owner_secret_hex> <agent_pubkey_hex> [conditions]

NIP-OA 背书原理

NIP-OA(Owner Attestation)的auth标签是长度为 4 的 JSON 数组:

["auth", "<owner-pubkey-hex>", "<conditions>", "<sig-hex>"]

签名构造规则(见 crates/buzz-sdk/src/nip_oa.rs):

preimage = "nostr:agent-auth:" || agent_pubkey_hex || ":" || conditions message = SHA256(preimage) sig = BIP-340 Schnorr(message, owner_secret_key)

conditions支持三类子句,用&连接:kind=<0-65535>created_at<<时间戳>created_at><时间戳>。空字符串表示无附加条件(Countdown Bot 使用此形式)。SDK 的校验器会拒绝含空白、前导零、非规范十进制或未知子句的条件;同时拒绝"所有者与代理同钥"的自背书(self-attestation),因为这种背书没有意义。中继在连接准入阶段通过 verify_auth_tag_for_auth_event 对签名 AUTH 事件的created_at逐条执行created_at</created_at>时间条件(严格不等,相等不通过)。

中继侧的前提条件

该路径要求中继开启相应开关(crates/buzz-relay/src/config.rs):

  • BUZZ_REQUIRE_RELAY_MEMBERSHIP=true——关闭中继上强制成员检查(默认false);
  • BUZZ_ALLOW_NIP_OA_AUTH=true——允许非成员的机器人密钥凭所有者背书接入(默认false);
  • 所有者公钥必须是活跃的中继成员

从配置文档可以看出设计意图:allow_nip_oa_auth = truerequire_relay_membership = true时,agent(此处为机器人)因所有者是中继成员而获得会话级(session-scoped)访问,而非持久成员身份。

中继访问 ≠ 频道访问

README 特别强调:中继访问和频道访问是相互独立的。owner-attested 认证只能把机器人放进中继,机器人发布事件时仍是自己的公钥。它启动时会尝试向开放频道自加入为bot成员;对于私有频道,必须由 owner/admin 先把机器人公钥加入频道成员,它才会出现在成员列表、能被提及解析命中,也才能读写消息。

环境变量速查表

环境变量必填说明
BUZZ_RELAY_URL可选中继 WebSocket 地址,默认ws://localhost:3000
BUZZ_CHANNEL_ID必填目标频道 UUID
BUZZ_BOT_PRIVATE_KEY必填机器人nsec或十六进制私钥
BUZZ_BOT_AUTH_MODE可选standalone(默认)或owner-attested,其他值直接报错退出
BUZZ_OWNER_PRIVATE_KEYowner-attested 且未提供标签时必填所有者/agent 私钥,用于生成 auth 标签
BUZZ_AUTH_TAG可选预计算好的 NIP-OA auth 标签 JSON,提供后不再读取BUZZ_OWNER_PRIVATE_KEY
BUZZ_REQUIRE_RELAY_MEMBERSHIP中继侧关闭中继上强制成员检查(默认false
BUZZ_ALLOW_NIP_OA_AUTH中继侧允许 owner-attested 非成员接入(默认false

环境变量解析逻辑集中在 Config::from_env:BUZZ_BOT_PRIVATE_KEYnostr::Keys::parse解析并校验格式,缺失时给出明确报错;BUZZ_BOT_AUTH_MODE被硬编码校验为standaloneowner-attested二值。

命令解析与回复逻辑

机器人支持两种触发方式,入口是 event_reply:

fn event_reply(config: &Config, event: &Event) -> Option<String> { command_reply(&event.content).or_else(|| { event_mentions_bot(event, config).then(|| mention_command_reply(&event.content))? }) }
  • 命令前缀方式(command_reply):按空白切分词元,第一个词是!countdown!fib、第二个词是数字时命中;
  • 提及方式(mention_command_reply):对词元做windows(2)滑动窗口,找到["countdown", n]["fib", n]组合即命中;是否算提及由 event_mentions_bot 判断——事件中必须存在p标签且其值等于机器人公钥。Buzz UI 在从 @ 自动补全选中机器人时会自动补上这个 p 标签,这正是 README 中"提及命令需要文本 + p 标签两者齐备"的协议依据。

数值统一走 parse_bounded 校验,范围是1..=100,越界或非数字统一回复"Please use a number from 1 to 100."。两个回复生成器:

  • 倒计时(1..=n).rev()生成降序数字序列,末尾拼接🚀
  • 斐波那契倒序:先正向递推count项(0,1,1,2,...),再整体reverse()——README 注明这是刻意设计:本示例是倒计时机器人,所以!fib也按倒序输出。

本地运行:五分钟跑通

  1. 启动 Buzz(仓库根目录):

    . ./bin/activate-hermit just setup just relay
  2. 在桌面端应用中创建或选择一个频道,复制其UUID

  3. 按上文任意一种认证路径运行机器人:

    BUZZ_RELAY_URL=ws://localhost:3000 \ BUZZ_CHANNEL_ID=<channel-uuid> \ BUZZ_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \ BUZZ_BOT_AUTH_MODE=standalone \ cargo run --manifest-path examples/countdown-bot/Cargo.toml

    启动日志会依次打印机器人公钥、连接目标、发布的 kind0资料、成员自加入结果,以及监听频道 ID。

  4. 在频道里发送以下任意命令,即可看到机器人回复:

    !countdown 5 !fib 8 @Countdown Bot fib 8

机器人通过Ctrl-C干净退出(事件循环中的tokio::signal::ctrl_c()分支)。依赖方面,Cargo.toml 只依赖nostrtokio-tungsteniteserde_jsonfutures-util等常规库,外加通过path = "../../crates/buzz-sdk"引用的本地buzz-sdkcrate(用于build_profilebuild_message与 NIP-OA 三件套)。

设计取舍与边界防护

README 的 Notes 部分总结了示例刻意遵循的边界,理解它们能帮你避免在自己机器人里踩坑:

  • 命令有上限!countdown!fib最大 100):单条消息不可能让机器人向中继刷屏;越界命令得到显式的帮助性回复。对应parse_bounded与三组单元测试。
  • !fib倒序输出:因为这是一个倒计时机器人,输出风格与主题保持一致。
  • 提及命令需要"文本 + p 标签"双条件:仅文本相似不会误触发;Buzz UI 的提及自动补全负责补 p 标签。
  • 忽略自身消息:杜绝自我反馈回环(源码中event.pubkey == bot_keys.public_key()即返回)。
  • 用直接 WebSocket + NIP-42 而非 MCP:让整条协议路径在单个小文件中可审查,作为协议教学与调试基准再合适不过。

从源码结构可以推断,这个示例的定位是"最小可运行的协议参考实现":它把 NIP-42 握手、kind0资料、kind9000成员、kind9消息收发这几个 Buzz 参与者必备的协议环节压缩进一个约 440 行的文件,并配以单元测试锁定行为——任何语言、任何框架想要接入 Buzz,都可以把它当作协议蓝本对照实现。

【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 16:57:47

ESP32蓝牙Beacon测距实战:RSSI转距离原理与校准滤波指南

搞 ESP32 开发的朋友应该都有这种感觉&#xff0c;从板子到手到真正让它按自己的想法跑起来&#xff0c;中间要翻过的山头真不少。之前几讲把环境搭建、GPIO、Wi-Fi 联网这些基础打完后&#xff0c;很多人在蓝牙这块又卡住了&#xff0c;尤其是想用蓝牙做点实际应用的时候&…

作者头像 李华
网站建设 2026/9/12 16:57:37

雷达Simulink仿真中的RF前端行为级建模与参数化实践

简介&#xff1a;这份资源面向雷达系统设计工程师与Simulink建模学习者&#xff0c;围绕射频前端行为在雷达系统级仿真中的整合展开&#xff0c;提供单站脉冲雷达目标探测与FMCW雷达距离/速度估计两套完整可运行模型。资源共9个文件&#xff0c;其中5个m脚本用于参数配置与仿真…

作者头像 李华
网站建设 2026/9/12 16:57:25

ESP32蓝牙Beacon测距实战:RSSI原理、代码实现与参数标定

1. 蓝牙beacon测距的总体设计思路开讲之前先说个事&#xff1a;这个系列的每一讲&#xff0c;我都在尽量控制篇幅&#xff0c;结果每讲写出来都能赶上小论文。倒不是因为废话多&#xff0c;而是ESP-IDF这套框架里&#xff0c;很多看起来“一行搞定”的功能&#xff0c;真要讲清…

作者头像 李华