Files
Cyrene_v2/backend/services/platform-bridge/bridge.go
T
Aska a58ec7325d
Cyrene CI / backend (push) Has been cancelled
Cyrene CI / frontend (push) Has been cancelled
refactor(logging): replace all print/log with structured slog logger
- main.go: 30+ log.Printf → logger.Infof
- plugin/registry: 12 log.Printf → logger.Infof
- db/db.go, ws/hub.go: log → logger
- platform-bridge: fmt.Printf/println → logger.Info
- Logs now: time=... level=INFO msg=...
- 16 packages pass, vet clean
2026-08-12 21:31:06 +08:00

140 lines
4.6 KiB
Go

// Package bridge provides multi-platform message bridging for Cyrene.
// It defines unified message types and adapter interfaces for QQ, Telegram, Discord, and Webhook.
package bridge
import (
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
)
// UnifiedMessage is the internal representation of a message from any platform.
type UnifiedMessage struct {
ID string `json:"id"`
Platform string `json:"platform"` // "qq" | "telegram" | "discord" | "webhook"
Source string `json:"source"` // Group ID or user ID
SenderID string `json:"sender_id"` // Platform-specific sender ID
SenderName string `json:"sender_name"` // Display name
Content string `json:"content"`
IsAtBot bool `json:"is_at_bot"` // Whether @bot was used
IsDM bool `json:"is_dm"` // Private message
IsAdmin bool `json:"is_admin"` // Sender is admin
Attachments []Attachment `json:"attachments,omitempty"`
RawData any `json:"raw_data,omitempty"` // Platform-specific raw payload
Timestamp time.Time `json:"timestamp"`
}
// Attachment represents a file, image, or voice attachment.
type Attachment struct {
Type string `json:"type"` // "image" | "file" | "voice" | "video"
URL string `json:"url"`
Mime string `json:"mime,omitempty"`
Size int64 `json:"size,omitempty"`
}
// SendOptions controls how a message is sent to a platform.
type SendOptions struct {
ReplyTo string // Message ID to reply to
MaxLength int // Max content length (default 500 for group chats)
MinGap time.Duration // Min interval between messages (default 3s for QQ groups)
}
// Adapter is the interface for all platform adapters.
type Adapter interface {
// Name returns the unique adapter identifier (e.g. "qq-main", "telegram-bot")
Name() string
// Platform returns the platform type: "qq", "telegram", "discord", "webhook"
Platform() string
// Start begins listening for messages (WebSocket connect / long polling / HTTP server)
Start() error
// Stop shuts down the adapter
Stop() error
// Send delivers a message to the platform
Send(target string, content string, opts SendOptions) error
// Incoming returns the channel of incoming messages from this adapter
Incoming() <-chan *UnifiedMessage
// IsConnected returns whether the adapter is currently connected
IsConnected() bool
}
// Router matches incoming messages to the AI pipeline and routes responses back.
type Router struct {
adapters map[string]Adapter
onMessage func(msg *UnifiedMessage) // Called when a message should be sent to AI-Core
}
// NewRouter creates a new bridge router.
func NewRouter() *Router {
return &Router{adapters: make(map[string]Adapter)}
}
// Register adds an adapter to the router.
func (r *Router) Register(a Adapter) {
r.adapters[a.Name()] = a
}
// SetMessageHandler sets the callback for incoming messages.
func (r *Router) SetMessageHandler(fn func(*UnifiedMessage)) {
r.onMessage = fn
}
// StartAll starts all registered adapters.
func (r *Router) StartAll() {
for name, a := range r.adapters {
go func(name string, a Adapter) {
if err := a.Start(); err != nil {
logger.Info("[bridge]", name, "start error:", err.Error())
return
}
for msg := range a.Incoming() {
if r.onMessage != nil {
r.onMessage(msg)
}
}
}(name, a)
}
}
// StopAll stops all adapters.
func (r *Router) StopAll() {
for _, a := range r.adapters {
a.Stop()
}
}
// SendTo routes a response message to the appropriate adapter.
func (r *Router) SendTo(platform, target, content string, opts SendOptions) error {
for _, a := range r.adapters {
if a.Platform() == platform {
return a.Send(target, content, opts)
}
}
return nil
}
// GroupChatRules defines the behavior constraints for group chat interactions.
type GroupChatRules struct {
MaxLength int // Max characters per message
MinGap time.Duration // Min interval between messages
MaxPerRound int // Max messages per conversation round
SilenceKeywords []string // Keywords that trigger silence mode ("闭嘴", "别说话")
AmbientMode bool // Whether to use ambient/审查 mode (reply only when mentioned or LLM decides)
}
// DefaultGroupRules returns sensible defaults for QQ group chats.
func DefaultGroupRules() GroupChatRules {
return GroupChatRules{
MaxLength: 500,
MinGap: 3 * time.Second,
MaxPerRound: 2,
SilenceKeywords: []string{"闭嘴", "别说话", "别说了", "安静", "shut up", "stop"},
AmbientMode: true,
}
}