OA0
OA0 是一个探索 AI 的社区
现在注册
已注册用户请  登录
OA0  ›  技能包  ›  towns-protocol:用于构建 Towns Protocol 机器人的 SDK 工具集

towns-protocol:用于构建 Towns Protocol 机器人的 SDK 工具集

 
  aspect ·  2026-02-07 20:12:36 · 10 次点击  · 0 条评论  

名称: bots
描述: >-
适用于构建 Towns Protocol 机器人 - 涵盖 SDK 初始化、斜杠命令、消息处理器、反应、交互式表单、区块链操作和部署。
触发词:"towns bot"、"makeTownsBot"、"onSlashCommand"、"onMessage"、"sendInteractionRequest"、
"webhook"、"bot deployment"、"@towns-protocol/bot"
许可证: MIT
compatibility: 需要 Bun 运行时、Base 网络 RPC 访问权限、@towns-protocol/bot SDK
元数据:
author: towns-protocol
version: "2.0.0"


Towns Protocol 机器人 SDK 参考

核心规则

必须遵守以下规则,违规将导致静默失败:

  1. 用户 ID 是以太坊地址 - 始终为 0x... 格式,切勿使用用户名。
  2. 提及需要同时满足两个条件 - 文本中使用 <@{userId}> 格式,并且在选项的 mentions 数组中包含该用户 ID。
  3. 双钱包架构
    • bot.viem.account.address = 燃料钱包(签名并支付燃料费)- 必须存入 Base ETH
    • bot.appAddress = 金库钱包(可选,用于转账)
  4. 斜杠命令不会触发 onMessage - 它们有独立的处理器。
  5. 交互式表单使用 type 属性 - 而不是 case(例如:type: 'form')。
  6. 切勿仅依赖 txHash - 在授予访问权限前,请验证 receipt.status === 'success'

快速参考

关键导入

import { makeTownsBot, getSmartAccountFromUserId } from '@towns-protocol/bot'
import type { BotCommand, BotHandler } from '@towns-protocol/bot'
import { Permission } from '@towns-protocol/web3'
import { parseEther, formatEther, erc20Abi, zeroAddress } from 'viem'
import { readContract, waitForTransactionReceipt } from 'viem/actions'
import { execute } from 'viem/experimental/erc7821'

处理器方法

方法 签名 说明
sendMessage (channelId, text, opts?) → { eventId } opts: { threadId?, replyId?, mentions?, attachments? }
editMessage (channelId, eventId, text) 仅限机器人自己的消息
removeEvent (channelId, eventId) 仅限机器人自己的消息
sendReaction (channelId, messageId, emoji)
sendInteractionRequest (channelId, payload) 表单、交易、签名
hasAdminPermission (userId, spaceId) → boolean
ban / unban (userId, spaceId) 需要 ModifyBanning 权限

机器人属性

属性 描述
bot.viem 用于区块链交互的 Viem 客户端
bot.viem.account.address 燃料钱包 - 必须存入 Base ETH
bot.appAddress 金库钱包(可选)
bot.botId 机器人标识符

详细指南请参阅 references/
- 消息 API - 提及、线程、附件、格式化
- 区块链操作 - 读写合约、验证交易
- 交互式组件 - 表单、交易请求
- 部署 - 本地开发、Render、隧道
- 调试 - 故障排除指南


机器人设置

项目初始化

bunx towns-bot init my-bot
cd my-bot
bun install

环境变量

APP_PRIVATE_DATA=<base64_credentials>   # 来自 app.towns.com/developer
JWT_SECRET=<webhook_secret>              # 至少 32 个字符
PORT=3000
BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/KEY  # 推荐

基础机器人模板

import { makeTownsBot } from '@towns-protocol/bot'
import type { BotCommand } from '@towns-protocol/bot'

const commands = [
  { name: 'help', description: '显示帮助' },
  { name: 'ping', description: '检查是否存活' }
] as const satisfies BotCommand[]

const bot = await makeTownsBot(
  process.env.APP_PRIVATE_DATA!,
  process.env.JWT_SECRET!,
  { commands }
)

bot.onSlashCommand('ping', async (handler, event) => {
  const latency = Date.now() - event.createdAt.getTime()
  await handler.sendMessage(event.channelId, 'Pong!延迟 ' + latency + 'ms')
})

export default bot.start()

配置验证

import { z } from 'zod'

const EnvSchema = z.object({
  APP_PRIVATE_DATA: z.string().min(1),
  JWT_SECRET: z.string().min(32),
  DATABASE_URL: z.string().url().optional()
})

const env = EnvSchema.safeParse(process.env)
if (!env.success) {
  console.error('配置无效:', env.error.issues)
  process.exit(1)
}

事件处理器

onMessage

在常规消息(斜杠命令)上触发。

bot.onMessage(async (handler, event) => {
  // event: { userId, spaceId, channelId, eventId, message, isMentioned, threadId?, replyId? }

  if (event.isMentioned) {
    await handler.sendMessage(event.channelId, '你提到了我!')
  }
})

onSlashCommand

/command 上触发。不会触发 onMessage

bot.onSlashCommand('weather', async (handler, { args, channelId }) => {
  // /weather San Francisco → args: ['San', 'Francisco']
  const location = args.join(' ')
  if (!location) {
    await handler.sendMessage(channelId, '用法:/weather <地点>')
    return
  }
  // ... 获取天气数据
})

onReaction

bot.onReaction(async (handler, event) => {
  // event: { reaction, messageId, channelId }
  if (event.reaction === '👋') {
    await handler.sendMessage(event.channelId, '我看到你挥手了!')
  }
})

onTip

需要在开发者门户中启用“所有消息”模式。

bot.onTip(async (handler, event) => {
  // event: { senderAddress, receiverAddress, amount (bigint), currency }
  if (event.receiverAddress === bot.appAddress) {
    await handler.sendMessage(event.channelId,
      '感谢你打赏 ' + formatEther(event.amount) + ' ETH!')
  }
})

onInteractionResponse

bot.onInteractionResponse(async (handler, event) => {
  switch (event.response.payload.content?.case) {
    case 'form':
      const form = event.response.payload.content.value
      for (const c of form.components) {
        if (c.component.case === 'button' && c.id === 'yes') {
          await handler.sendMessage(event.channelId, '你点击了“是”!')
        }
      }
      break
    case 'transaction':
      const tx = event.response.payload.content.value
      if (tx.txHash) {
        // **重要**:在授予访问权限前,先在链上验证
        // 完整验证模式请参阅 references/BLOCKCHAIN.md
        await handler.sendMessage(event.channelId,
          '交易:https://basescan.org/tx/' + tx.txHash)
      }
      break
  }
})

事件上下文验证

在使用前始终验证上下文:

bot.onSlashCommand('cmd', async (handler, event) => {
  if (!event.spaceId || !event.channelId) {
    console.error('上下文缺失:', { userId: event.userId })
    return
  }
  // 可以安全继续
})

常见错误

错误 修复方法
insufficient funds for gas bot.viem.account.address 存入 Base ETH
提及未高亮显示 文本中同时包含 <@userId> 并且mentions 数组中包含该用户 ID
斜杠命令不工作 makeTownsBotcommands 数组中添加命令
处理器未触发 检查开发者门户中的消息转发模式
writeContract 失败 对于外部合约,请使用 execute()
仅凭 txHash 就授予访问权限 先验证 receipt.status === 'success'
消息行重叠 使用 \n\n(双换行符),而不是 \n
缺少事件上下文 在使用前验证 spaceId/channelId

资源

  • 开发者门户:https://app.towns.com/developer
  • 文档:https://docs.towns.com/build/bots
  • SDK:https://www.npmjs.com/package/@towns-protocol/bot
  • 链 ID:8453 (Base 主网)
10 次点击  ∙  0 人收藏  
登录后收藏  
0 条回复
关于 ·  帮助 ·  PING ·  隐私政策 ·  服务条款   
OA0 - Omni AI 0 一个探索 AI 的社区
沪ICP备2024103595号-2
耗时 44 ms
Developed with Cursor