bot

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package bot 实现 fairpeer 多渠道 IM bot 消息网关,支持 QQ、飞书、微信、Telegram。 架构参考 Hermes Agent 的 gateway/adapter/session 模式。

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildSessionKey

func BuildSessionKey(src SessionSource) string

BuildSessionKey 根据 Hermes 模式生成稳定的 session key:

  • DM:按 chat 隔离(同一 DM 会话共享历史)
  • 群聊:按 user 隔离(每人独立会话)
  • thread:共享(thread 内所有人共享上下文)

func IsSlashBypass

func IsSlashBypass(text string) bool

IsSlashBypass 判断消息是否为绕过队列的斜杠命令。

Types

type Adapter

type Adapter interface {
	// Platform 返回平台标识。
	Platform() Platform

	// Start 启动适配器,连接平台 gateway。
	Start(ctx context.Context) error

	// Stop 优雅关闭适配器。
	Stop() error

	// Send 发送一条出站消息。
	Send(ctx context.Context, msg OutboundMessage) (SendResult, error)

	// SendTyping 发送"正在输入"状态。
	SendTyping(ctx context.Context, chatID string) error

	// Messages 返回入站消息通道。
	Messages() <-chan InboundMessage

	// Name 返回适配器实例名(用于日志)。
	Name() string
}

Adapter 是平台适配器接口,每个平台实现一个。

type AllowlistConfig

type AllowlistConfig struct {
	Enabled  bool
	AllowAll bool
	Mode     string // "open"(自动加入)| "review"(需审批)
	Users    map[Platform][]string
	Groups   map[Platform][]string
}

AllowlistConfig 控制哪些用户/群可以使用 bot。

type BotGateway

type BotGateway struct {
	// contains filtered or unexported fields
}

BotGateway 是 fairpeer bot 消息网关,管理 Controller 生命周期、session 并发、 事件渲染和平台适配器。

func NewGateway

func NewGateway(cfg GatewayConfig, adapters map[Platform]Adapter, logger *slog.Logger) *BotGateway

NewGateway 创建一个新的 BotGateway。

func (*BotGateway) Push

func (gw *BotGateway) Push(ctx context.Context, dest, text string) error

Push sends a text message to a specific chat on a given platform, independent of the inbound-message flow. Used by the scheduler and calendar reminder engine to deliver results to IM (OutputMode="im").

dest formats:

  • "platform:chatID" — e.g. "feishu:oc_xxx", "weixin:wxid_xxx". ChatType is left empty; feishu/weixin route by ChatID alone.
  • "platform:chatType:chatID" — e.g. "qq:group:xxxx". Needed for QQ, whose send URL is chosen by ChatType (dm/group/guild/direct). Omitting chatType for QQ defaults to dm, which fails for group/channel IDs.

No-op (returns nil) if the platform adapter isn't connected — a scheduled push shouldn't fail the task run just because IM is offline.

func (*BotGateway) RecentChats

func (gw *BotGateway) RecentChats() []RecentChat

RecentChats returns a snapshot of recently-seen chats, newest first. Used by the desktop layer to populate the IM-target picker in the task form.

func (*BotGateway) Start

func (gw *BotGateway) Start(ctx context.Context) error

Start 启动所有已启用的平台适配器并开始处理消息。

func (*BotGateway) Stop

func (gw *BotGateway) Stop()

Stop 停止所有适配器并关闭所有 session。

type ChannelConfig

type ChannelConfig struct {
	Model         string
	WorkspaceRoot string
}

ChannelConfig overrides gateway defaults for one IM channel.

type ChatType

type ChatType string

ChatType 标识会话类型。

const (
	ChatDM     ChatType = "dm"
	ChatGroup  ChatType = "group"
	ChatGuild  ChatType = "guild"
	ChatDirect ChatType = "direct"
	ChatThread ChatType = "thread"
)

type DesktopBridge added in v0.2.0

type DesktopBridge interface {
	// Sessions 枚举当前所有桌面 live 会话。
	Sessions() []DesktopSessionInfo
	// SetWatch 订阅/退订当前聊天的桌面事件推送(审批请求、任务完成/出错)。
	SetWatch(route DesktopWatchRoute, enable bool) error
	// Watching 返回该聊天当前是否在订阅。
	Watching(route DesktopWatchRoute) bool
	// Approve 应答任意桌面会话的待审批项,返回用户可读的结果文案。
	Approve(approvalID string, allow bool) (string, error)
	// AskQuestions 返回某个待回答 ask 的问题列表(用于把 IM 文本解析成选项)。
	AskQuestions(askID string) ([]event.AskQuestion, bool)
	// Answer 应答任意桌面会话的待回答 ask,返回用户可读的结果文案。
	Answer(askID string, answers []event.AskAnswer) (string, error)
}

DesktopBridge 由桌面端进程实现,让 bot 聊天获得对整个桌面端的上帝视角: 全局会话清单、事件订阅、以及对任意桌面 live 会话的远程审批/问答。

审批应答与桌面 UI 是"先到者赢"(controller 侧幂等,重复应答被静默忽略), Approve/Answer 的返回文案应体现"以先到者为准"。

type DesktopPendingInfo added in v0.2.0

type DesktopPendingInfo struct {
	ID   string
	Kind string // "approval" | "ask"
	Tool string
}

DesktopPendingInfo 是一条待处理的审批或问答的摘要。

type DesktopSessionInfo added in v0.2.0

type DesktopSessionInfo struct {
	TabID         string
	Label         string
	Workspace     string
	Topic         string
	Ready         bool
	Running       bool
	PendingPrompt bool
	// Pending 列出该会话当前待处理的审批/问答,便于用户在推送丢失时仍能
	// 用 /desktop approve|answer <id> 处理。
	Pending []DesktopPendingInfo
}

DesktopSessionInfo 是一个桌面 live 会话(tab)的快照,用于 /desktop status。

type DesktopWatchRoute added in v0.2.0

type DesktopWatchRoute struct {
	Platform Platform
	ChatType ChatType
	ChatID   string
}

DesktopWatchRoute 标识一个订阅了桌面事件的 bot 聊天。

func (DesktopWatchRoute) Key added in v0.2.0

func (r DesktopWatchRoute) Key() string

Key 返回订阅表的稳定键。

type GatewayConfig

type GatewayConfig struct {
	Model         string
	MaxSteps      int
	WorkspaceRoot string
	Channels      map[Platform]ChannelConfig
	Allowlist     AllowlistConfig
	Enabled       map[Platform]bool
	Debounce      time.Duration
	// SessionIdleTimeout controls how long a bot session can sit idle (no
	// incoming messages) before its controller is closed to reclaim memory.
	// A busy bot serving many users/-groups would otherwise keep every
	// controller alive forever. Default 30m; 0 disables reaping.
	SessionIdleTimeout time.Duration
	// AllowlistSaver 当新用户被自动加入白名单时调用,用于持久化。
	// 参数为更新后的完整 AllowlistConfig。nil 表示不持久化。
	AllowlistSaver func(AllowlistConfig)
	// OnTurnFinished 在一轮对话结束后调用(无论成功或出错),用于让上层(desktop)
	// 把对话方的会话来源(platform/chatType/chatID/userID)和本次会话的本地 transcript
	// 路径回写到 BotConnection 的 SessionMappings —— 否则只有手动「测试连接」才记录
	// remoteID、而 SessionID(本地话题)永远为空,UI 显示「等待首条消息」。传整个
	// SessionSource 而非单个 remoteID,是为了让 chatType/chatID 也带上:同一人在不同
	// 群里的对话才能分开。放在 turn 结束后是因为 sessionPath 要等首轮 RunTurn 才确定
	// (prewarm 时为空)。nil 表示不回写。
	OnTurnFinished func(src SessionSource, sessionPath string)
	// Netdev,非 nil 时启用 /netdev 系列命令(发现列表 / 证据详情)——
	// 运维告警与早报推送的回话侧(FDE 的耳朵)。
	Netdev NetdevBridge
	// Desktop,非 nil 时启用 /desktop 系列命令(远程观察 + 审批桌面 live 会话)。
	// 由桌面端进程注入;独立 bot 进程(无桌面)保持 nil,/desktop 会提示不可用。
	Desktop DesktopBridge
}

GatewayConfig 是 BotGateway 的配置。

type InboundMessage

type InboundMessage struct {
	Platform  Platform `json:"platform"`
	ChatType  ChatType `json:"chat_type"`
	ChatID    string   `json:"chat_id"`
	UserID    string   `json:"user_id"`
	UserName  string   `json:"user_name"`
	Text      string   `json:"text"`
	MessageID string   `json:"message_id"`
	ThreadID  string   `json:"thread_id,omitempty"`
	MediaURLs []string `json:"media_urls,omitempty"`
	Raw       any      `json:"-"`
}

InboundMessage 是从任一平台收到的入站消息。

func (InboundMessage) Session

func (m InboundMessage) Session() SessionSource

Session derives the SessionSource from this message.

type InlineKeyboard

type InlineKeyboard struct {
	Rows []InlineKeyboardRow `json:"rows"`
}

InlineKeyboard 是内联键盘(用于 QQ 审批)。

type InlineKeyboardButton

type InlineKeyboardButton struct {
	ID         string `json:"id"`
	Label      string `json:"label"`
	Style      int    `json:"style,omitempty"` // 0=default, 1=primary, 2=danger
	CallbackID string `json:"callback_id,omitempty"`
}

InlineKeyboardButton 是一个按钮。

type InlineKeyboardRow

type InlineKeyboardRow struct {
	Buttons []InlineKeyboardButton `json:"buttons"`
}

InlineKeyboardRow 是一行按钮。

type InteractiveCard

type InteractiveCard struct {
	Header   string                   `json:"header"`
	Elements []InteractiveCardElement `json:"elements"`
}

InteractiveCard 是交互式卡片(用于飞书审批/问答)。

type InteractiveCardElement

type InteractiveCardElement struct {
	Tag     string         `json:"tag"`
	Content string         `json:"content,omitempty"`
	Extra   map[string]any `json:"extra,omitempty"`
}

InteractiveCardElement 是卡片内元素。

type MessageHandler

type MessageHandler func(ctx context.Context, msg InboundMessage)

MessageHandler 是 BotGateway 处理入站消息的回调。

type NetdevActionResult added in v0.2.0

type NetdevActionResult struct {
	OK  bool
	Msg string
}

NetdevActionResult reports a mutating IM command's outcome in human text.

type NetdevBridge added in v0.2.0

type NetdevBridge interface {
	// NetdevActiveFindings lists unresolved findings, newest first.
	NetdevActiveFindings() []NetdevFindingSummary
	// NetdevFindingByID renders one finding (NotFound=true when absent).
	NetdevFindingByID(id string) NetdevFindingDetail
	// NetdevAckFinding acknowledges one finding from IM (completion-spec §5.3:
	// 收到告警后可操作——ack 不解决,只标记已被人看过).
	NetdevAckFinding(id string) NetdevActionResult
	// NetdevProposals lists proposals pending a human decision.
	NetdevProposals() []NetdevProposalSummary
	// NetdevProposalApprove / NetdevProposalReject run the SAME guarded path
	// as the desktop approval UI (group policy + change window + audit).
	NetdevProposalApprove(id string) NetdevActionResult
	NetdevProposalReject(id, reason string) NetdevActionResult
}

NetdevBridge is the netdev-side surface the desktop host injects.

type NetdevEvidenceView added in v0.2.0

type NetdevEvidenceView struct {
	Device  string
	Command string
	Output  string
}

NetdevEvidenceView is one evidence excerpt.

type NetdevFindingDetail added in v0.2.0

type NetdevFindingDetail struct {
	ID       string
	Severity string
	Title    string
	Devices  []string
	Detail   string
	Status   string
	Evidence []NetdevEvidenceView
	NotFound bool
}

NetdevFindingDetail is one finding's full text for 「详情」.

type NetdevFindingSummary added in v0.2.0

type NetdevFindingSummary struct {
	ID       string
	Severity string
	Title    string
	Devices  []string
	Status   string
}

NetdevFindingSummary is one row of 「发现」.

type NetdevProposalSummary added in v0.2.0

type NetdevProposalSummary struct {
	ID     string
	Status string
	Title  string
}

NetdevProposalSummary is one row of 「变更」 for the IM command surface.

type OutboundMessage

type OutboundMessage struct {
	ChatID       string           `json:"chat_id"`
	ChatType     ChatType         `json:"chat_type,omitempty"`
	Text         string           `json:"text,omitempty"`
	MediaURLs    []string         `json:"media_urls,omitempty"`
	ReplyToMsgID string           `json:"reply_to_msg_id,omitempty"`
	Keyboard     *InlineKeyboard  `json:"keyboard,omitempty"`
	Card         *InteractiveCard `json:"card,omitempty"`
}

OutboundMessage 是发送到平台的消息。

type Platform

type Platform string

Platform 标识 IM 平台。

const (
	PlatformQQ       Platform = "qq"
	PlatformFeishu   Platform = "feishu"
	PlatformWeixin   Platform = "weixin"
	PlatformTelegram Platform = "telegram"
)

type RecentChat

type RecentChat struct {
	Platform Platform `json:"platform"`
	ChatType ChatType `json:"chatType"`
	ChatID   string   `json:"chatId"`
	UserName string   `json:"userName"`
	LastSeen int64    `json:"lastSeen"` // unix seconds
}

RecentChat is one entry in the gateway's "recently seen chats" ring buffer, surfaced to the frontend so the user can pick an IM destination for scheduled tasks and calendar reminders without hand-typing a chatID. UserName is the best available display name (private chat = the user's name; group chat may be empty when the platform doesn't expose group names).

type SendResult

type SendResult struct {
	MessageID string `json:"message_id,omitempty"`
	Err       error  `json:"err,omitempty"`
}

SendResult 是发送消息的结果。

type SessionManager

type SessionManager struct {
	// contains filtered or unexported fields
}

SessionManager 管理 session 级别的并发控制:同一 session 同时只跑一个任务。

func NewSessionManager

func NewSessionManager(debounce time.Duration) *SessionManager

NewSessionManager 创建一个新的 session 管理器。debounce 是消息合并窗口。

func (*SessionManager) ActiveCount

func (sm *SessionManager) ActiveCount() int

ActiveCount 返回当前活跃 session 数。

func (*SessionManager) ForceRelease

func (sm *SessionManager) ForceRelease(key string)

ForceRelease 强制释放 session(用于 session 关闭或错误恢复)。

func (*SessionManager) IsActive

func (sm *SessionManager) IsActive(key string) bool

IsActive 返回 session 是否有正在运行的任务。

func (*SessionManager) Release

func (sm *SessionManager) Release(key string) *InboundMessage

Release 释放 session 锁,返回等待队列中的下一条消息(合并后)。

func (*SessionManager) TryAcquire

func (sm *SessionManager) TryAcquire(key string, msg InboundMessage) (acquired bool, merged bool)

TryAcquire 尝试获取 session 锁。如果 session 正忙且消息非绕过命令,返回 false。 返回 (acquired, merged) — merged 为 true 表示消息已合并到等待队列。

type SessionSource

type SessionSource struct {
	Platform Platform `json:"platform"`
	ChatType ChatType `json:"chat_type"`
	ChatID   string   `json:"chat_id"`
	UserID   string   `json:"user_id"`
	ThreadID string   `json:"thread_id,omitempty"`
}

SessionSource 是会话的复合标识,用于生成稳定的 session key。

Directories

Path Synopsis
Package feishu 实现飞书自建应用 Bot 适配器。
Package feishu 实现飞书自建应用 Bot 适配器。
Package qq 实现 QQ 官方 Bot API v2 适配器。
Package qq 实现 QQ 官方 Bot API v2 适配器。
Package telegram 实现 Telegram Bot API 适配器。
Package telegram 实现 Telegram Bot API 适配器。
Package weixin 实现微信 iLink Bot 适配器。
Package weixin 实现微信 iLink Bot 适配器。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL