money/matrix.go

187 lines
4.8 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"log"
"net/url"
"strings"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// ================== Invio Matrix (usa SOLO MessageBundle) ==================
func notifyMatrix(cfg Config, mb MessageBundle) {
// Controllo configurazione (usa sanitize definita altrove, es. util.go)
if missing := missingMatrixConfig(cfg); len(missing) > 0 {
log.Printf("matrix: configurazione incompleta: mancano %v; messaggio non inviato. anteprima=%s",
missing, sanitize(mb.DecisionLine))
return
}
// Compose finale dal bundle (Compose è definito in messages.go)
final := mb.Compose()
// Login
cli, err := loginMatrix(cfg.MatrixHS, cfg.MatrixUser, cfg.MatrixPass, cfg.MatrixDeviceName)
if err != nil {
log.Printf("matrix: login fallito: %v; testo=%s", err, sanitize(final))
return
}
// Resolve + invio
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
roomID, err := resolveRoomID(ctx, cli, cfg.MatrixRoom)
if err != nil {
log.Printf("matrix: risoluzione stanza fallita: %v; input=%q testo=%s", err, cfg.MatrixRoom, sanitize(final))
return
}
if err := sendMatrixMessage(cli, roomID, final); err != nil {
log.Printf("matrix: invio messaggio fallito: %v; stanza=%s testo=%s", err, roomID, sanitize(final))
}
}
// ================== Matrix helpers ==================
func loginMatrix(hs, user, pass, deviceName string) (*mautrix.Client, error) {
if hs == "" || user == "" || pass == "" {
return nil, fmt.Errorf("config Matrix incompleta (server/user/pass)")
}
if deviceName == "" {
deviceName = "daemon-bot"
}
cli, err := mautrix.NewClient(hs, "", "")
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
resp, err := cli.Login(ctx, &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{
Type: mautrix.IdentifierTypeUser,
User: user,
},
Password: pass,
InitialDeviceDisplayName: deviceName,
})
if err != nil {
return nil, err
}
cli.AccessToken = resp.AccessToken
cli.UserID = resp.UserID
cli.DeviceID = resp.DeviceID
log.Printf("matrix: login eseguito user=%s device=%s", resp.UserID, resp.DeviceID)
return cli, nil
}
func resolveRoomID(ctx context.Context, cli *mautrix.Client, roomInput string) (id.RoomID, error) {
roomInput = strings.TrimSpace(roomInput)
if roomInput == "" {
return "", errors.New("stanza non specificata")
}
// RoomID diretto
if strings.HasPrefix(roomInput, "!") {
return id.RoomID(roomInput), nil
}
// Permalink matrix.to → estrai ID o alias
if strings.HasPrefix(roomInput, "http://") || strings.HasPrefix(roomInput, "https://") {
if rid, alias, ok := extractFromPermalink(roomInput); ok {
if rid != "" {
return id.RoomID(rid), nil
}
roomInput = alias
}
}
// Alias → resolve → join best-effort
if strings.HasPrefix(roomInput, "#") {
alias := id.RoomAlias(roomInput)
res, err := cli.ResolveAlias(ctx, alias)
if err != nil {
return "", fmt.Errorf("impossibile risolvere alias %s: %w", alias, err)
}
if _, err := cli.JoinRoomByID(ctx, res.RoomID); err != nil {
log.Printf("matrix: join by ID fallito (forse già membro): %v", err)
}
return res.RoomID, nil
}
return "", fmt.Errorf("formato stanza non supportato: usa ID (!...), alias (#...) o permalink matrix.to")
}
func extractFromPermalink(u string) (string, string, bool) {
parsed, err := url.Parse(u)
if err != nil {
return "", "", false
}
path := parsed.EscapedPath()
if !strings.HasPrefix(path, "/#/") {
return "", "", false
}
raw := strings.TrimPrefix(path, "/#/")
raw, _ = url.PathUnescape(raw)
if strings.HasPrefix(raw, "!") {
return raw, "", true
}
if strings.HasPrefix(raw, "#") {
return "", raw, true
}
return "", "", false
}
func sendMatrixMessage(cli *mautrix.Client, roomID id.RoomID, text string) error {
if cli == nil {
return fmt.Errorf("matrix client nullo")
}
if roomID == "" {
return fmt.Errorf("roomID mancante")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
_, err := cli.SendMessageEvent(
ctx,
roomID,
event.EventMessage,
&event.MessageEventContent{
MsgType: event.MsgText,
Body: text,
},
)
if err != nil {
return err
}
log.Printf("matrix: messaggio inviato stanza=%s lunghezza=%d", roomID, len(text))
return nil
}
func missingMatrixConfig(cfg Config) []string {
var missing []string
if strings.TrimSpace(cfg.MatrixHS) == "" {
missing = append(missing, "MATRIX_HS")
}
if strings.TrimSpace(cfg.MatrixUser) == "" {
missing = append(missing, "MATRIX_USER")
}
if strings.TrimSpace(cfg.MatrixPass) == "" {
missing = append(missing, "MATRIX_PASS")
}
if strings.TrimSpace(cfg.MatrixRoom) == "" {
missing = append(missing, "MATRIX_ROOM")
}
return missing
}