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 }