Repo iniciado
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// Package matrix wraps mautrix-go for agent use.
|
||||
package matrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"github.com/enmanuel/agents/internal/config"
|
||||
)
|
||||
|
||||
// Client wraps a mautrix client with agent-relevant helpers.
|
||||
type Client struct {
|
||||
raw *mautrix.Client
|
||||
cfg config.MatrixCfg
|
||||
}
|
||||
|
||||
// New creates and authenticates a Matrix client from config.
|
||||
// The access token is read from the env var specified in cfg.AccessTokenEnv.
|
||||
func New(cfg config.MatrixCfg) (*Client, error) {
|
||||
token := os.Getenv(cfg.AccessTokenEnv)
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("env var %s is not set", cfg.AccessTokenEnv)
|
||||
}
|
||||
|
||||
raw, err := mautrix.NewClient(cfg.Homeserver, id.UserID(cfg.UserID), token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create matrix client: %w", err)
|
||||
}
|
||||
|
||||
if cfg.DeviceID != "" {
|
||||
raw.DeviceID = id.DeviceID(cfg.DeviceID)
|
||||
}
|
||||
|
||||
return &Client{raw: raw, cfg: cfg}, nil
|
||||
}
|
||||
|
||||
// SendText sends a plain-text message to a room.
|
||||
func (c *Client) SendText(ctx context.Context, roomID, text string) error {
|
||||
_, err := c.raw.SendText(ctx, id.RoomID(roomID), text)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendMarkdown sends a formatted (Markdown) message to a room.
|
||||
func (c *Client) SendMarkdown(ctx context.Context, roomID, markdown string) error {
|
||||
content := event.MessageEventContent{
|
||||
MsgType: event.MsgText,
|
||||
Body: markdown,
|
||||
Format: event.FormatHTML,
|
||||
FormattedBody: markdown, // mautrix can render markdown -> HTML if needed
|
||||
}
|
||||
_, err := c.raw.SendMessageEvent(ctx, id.RoomID(roomID), event.EventMessage, content)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendReaction sends a reaction to an event.
|
||||
func (c *Client) SendReaction(ctx context.Context, roomID, eventID, reaction string) error {
|
||||
_, err := c.raw.SendReaction(ctx, id.RoomID(roomID), id.EventID(eventID), reaction)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendTyping sets the typing indicator in a room.
|
||||
func (c *Client) SendTyping(ctx context.Context, roomID string, typing bool) error {
|
||||
_, err := c.raw.UserTyping(ctx, id.RoomID(roomID), typing, 5000)
|
||||
return err
|
||||
}
|
||||
|
||||
// Raw returns the underlying mautrix.Client for advanced use.
|
||||
func (c *Client) Raw() *mautrix.Client {
|
||||
return c.raw
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package matrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"github.com/enmanuel/agents/internal/config"
|
||||
"github.com/enmanuel/agents/pkg/decision"
|
||||
"github.com/enmanuel/agents/pkg/message"
|
||||
)
|
||||
|
||||
// EventHandler is called for each incoming Matrix message that passes filters.
|
||||
type EventHandler func(ctx context.Context, msgCtx decision.MessageContext, evt *event.Event)
|
||||
|
||||
// Listener attaches to a mautrix syncer and dispatches events to an EventHandler.
|
||||
type Listener struct {
|
||||
client *Client
|
||||
cfg config.MatrixCfg
|
||||
handler EventHandler
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
// NewListener creates a Listener for the given client.
|
||||
func NewListener(client *Client, cfg config.MatrixCfg, handler EventHandler, logger *slog.Logger) *Listener {
|
||||
return &Listener{
|
||||
client: client,
|
||||
cfg: cfg,
|
||||
handler: handler,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the Matrix sync loop. Blocks until ctx is cancelled.
|
||||
func (l *Listener) Run(ctx context.Context) error {
|
||||
syncer := l.client.raw.Syncer.(*mautrix.DefaultSyncer)
|
||||
|
||||
syncer.OnEventType(event.EventMessage, func(ctx context.Context, evt *event.Event) {
|
||||
if !l.shouldHandle(evt) {
|
||||
return
|
||||
}
|
||||
|
||||
body, ok := evt.Content.Raw["body"].(string)
|
||||
if !ok || body == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine power level (simplified — full impl fetches from room state)
|
||||
powerLevel := 0
|
||||
|
||||
isDM := false // TODO: detect DMs via room member count
|
||||
|
||||
opts := message.ParseOptions{
|
||||
CommandPrefix: l.cfg.Filters.CommandPrefix,
|
||||
BotUserID: l.cfg.UserID,
|
||||
}
|
||||
|
||||
msgCtx := message.Parse(
|
||||
body,
|
||||
evt.Sender.String(),
|
||||
evt.RoomID.String(),
|
||||
powerLevel,
|
||||
isDM,
|
||||
opts,
|
||||
)
|
||||
|
||||
go l.handler(ctx, msgCtx, evt)
|
||||
})
|
||||
|
||||
l.logger.Info("starting matrix sync")
|
||||
return l.client.raw.SyncWithContext(ctx)
|
||||
}
|
||||
|
||||
// shouldHandle applies the configured filters to an event.
|
||||
func (l *Listener) shouldHandle(evt *event.Event) bool {
|
||||
f := l.cfg.Filters
|
||||
|
||||
// Don't handle our own messages
|
||||
if evt.Sender == id.UserID(l.cfg.UserID) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore bots
|
||||
if f.IgnoreBots && strings.HasSuffix(evt.Sender.String(), "-bot:"+serverName(l.cfg.UserID)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore specific users
|
||||
for _, u := range f.IgnoreUsers {
|
||||
if evt.Sender.String() == u {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if room is in the listen list
|
||||
if len(l.cfg.Rooms.Listen) > 0 {
|
||||
allowed := false
|
||||
for _, r := range l.cfg.Rooms.Listen {
|
||||
if evt.RoomID.String() == r {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func serverName(userID string) string {
|
||||
parts := strings.SplitN(userID, ":", 2)
|
||||
if len(parts) == 2 {
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user