- Add OTLP/gRPC observability config and resource attributes - Instrument webhook and onboarding handlers with tracing and metrics - Add OpenTelemetry dependencies to go.mod/go.sum - Update NixOS modules to configure observability settings
171 lines
4.9 KiB
Go
171 lines
4.9 KiB
Go
package onboarding
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
|
|
"maunium.net/go/mautrix/bridgev2"
|
|
"maunium.net/go/mautrix/bridgev2/bridgeconfig"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/format"
|
|
"maunium.net/go/mautrix/id"
|
|
|
|
"sneeuwvlok/packages/arrtrix/pkg/observability"
|
|
)
|
|
|
|
const handledInviteEventType = "com.arrtrix.handled_invite"
|
|
|
|
func HandleBotInvite(ctx context.Context, bridge *bridgev2.Bridge, texts bridgeconfig.ManagementRoomTexts, evt *event.Event) {
|
|
if evt.Type != event.StateMember ||
|
|
evt.GetStateKey() != bridge.Bot.GetMXID().String() ||
|
|
evt.Content.AsMember().Membership != event.MembershipInvite {
|
|
return
|
|
}
|
|
|
|
ctx, span := observability.StartSpan(ctx, "arrtrix.matrix.invite")
|
|
defer span.End()
|
|
span.SetAttributes(
|
|
attribute.String("matrix.room_id", evt.RoomID.String()),
|
|
attribute.String("matrix.sender", evt.Sender.String()),
|
|
)
|
|
outcome := "ignored"
|
|
defer observability.RecordInvite(ctx, outcome)
|
|
|
|
log := zerolog.Ctx(ctx)
|
|
sender, err := bridge.GetUserByMXID(ctx, evt.Sender)
|
|
if err != nil {
|
|
outcome = "user_lookup_failed"
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
log.Err(err).Msg("Failed to load sender for bot invite")
|
|
return
|
|
}
|
|
if !sender.Permissions.Commands {
|
|
outcome = "permission_denied"
|
|
span.SetStatus(codes.Error, "sender lacks command permission")
|
|
return
|
|
}
|
|
|
|
if err = bridge.Bot.EnsureJoined(ctx, evt.RoomID); err != nil {
|
|
outcome = "join_failed"
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
log.Err(err).Msg("Failed to accept invite to room")
|
|
return
|
|
}
|
|
|
|
members, err := bridge.Matrix.GetMembers(ctx, evt.RoomID)
|
|
if err != nil {
|
|
outcome = "member_lookup_failed"
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
log.Err(err).Msg("Failed to get members of room after accepting invite")
|
|
return
|
|
}
|
|
if len(members) != 2 {
|
|
outcome = "non_management_room"
|
|
span.SetStatus(codes.Error, "invite room is not a direct management room")
|
|
return
|
|
}
|
|
|
|
assignedManagementRoom := sender.ManagementRoom == ""
|
|
if assignedManagementRoom {
|
|
sender.ManagementRoom = evt.RoomID
|
|
if err = sender.Save(ctx); err != nil {
|
|
outcome = "management_room_save_failed"
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
log.Err(err).Msg("Failed to update user's management room in database")
|
|
return
|
|
}
|
|
}
|
|
|
|
message := buildWelcomeMessage(bridge, texts, sender, assignedManagementRoom)
|
|
content := format.RenderMarkdown(message, true, false)
|
|
if _, err = bridge.Bot.SendMessage(ctx, evt.RoomID, event.EventMessage, &event.Content{Parsed: &content}, nil); err != nil {
|
|
outcome = "welcome_send_failed"
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, err.Error())
|
|
log.Err(err).Msg("Failed to send welcome message to room")
|
|
return
|
|
}
|
|
|
|
outcome = "welcomed"
|
|
span.SetStatus(codes.Ok, "")
|
|
evt.Type = event.Type{Type: handledInviteEventType}
|
|
}
|
|
|
|
func buildWelcomeMessage(bridge *bridgev2.Bridge, texts bridgeconfig.ManagementRoomTexts, sender *bridgev2.User, assignedManagementRoom bool) string {
|
|
return composeWelcomeMessage(
|
|
bridge.Network.GetName().DisplayName,
|
|
bridge.Config.CommandPrefix,
|
|
bridge.Bot.GetMXID(),
|
|
texts,
|
|
sender.GetDefaultLogin() != nil,
|
|
assignedManagementRoom,
|
|
)
|
|
}
|
|
|
|
func composeWelcomeMessage(
|
|
bridgeName string,
|
|
commandPrefix string,
|
|
botMXID id.UserID,
|
|
texts bridgeconfig.ManagementRoomTexts,
|
|
connected bool,
|
|
assignedManagementRoom bool,
|
|
) string {
|
|
replacer := strings.NewReplacer(
|
|
"$cmdprefix", commandPrefix,
|
|
"$bridge", bridgeName,
|
|
"$bot", string(botMXID),
|
|
)
|
|
|
|
var parts []string
|
|
|
|
base := strings.TrimSpace(texts.Welcome)
|
|
if base == "" {
|
|
base = fmt.Sprintf("Hello, I'm the %s bot.", bridgeName)
|
|
}
|
|
parts = append(parts, replacer.Replace(base))
|
|
|
|
if assignedManagementRoom {
|
|
parts = append(parts, "This room has been marked as your management room.")
|
|
} else {
|
|
parts = append(parts, fmt.Sprintf("Use `%s help` to see available commands in this room.", commandPrefix))
|
|
}
|
|
|
|
if connected {
|
|
connected := strings.TrimSpace(texts.WelcomeConnected)
|
|
if connected == "" {
|
|
connected = "You're connected. Use `help` to see the commands available right now."
|
|
}
|
|
parts = append(parts, replacer.Replace(connected))
|
|
} else {
|
|
unconnected := strings.TrimSpace(texts.WelcomeUnconnected)
|
|
if unconnected == "" {
|
|
unconnected = "Use `help` to see the commands available right now."
|
|
}
|
|
parts = append(parts, replacer.Replace(unconnected))
|
|
}
|
|
|
|
if extra := strings.TrimSpace(texts.AdditionalHelp); extra != "" {
|
|
parts = append(parts, replacer.Replace(extra))
|
|
}
|
|
|
|
return strings.Join(parts, "\n\n")
|
|
}
|
|
|
|
func IsHandledInviteEvent(evt *event.Event) bool {
|
|
return evt.Type.Type == handledInviteEventType
|
|
}
|
|
|
|
func IsBotInviteFor(roomBot id.UserID, evt *event.Event) bool {
|
|
return evt.Type == event.StateMember &&
|
|
evt.GetStateKey() == roomBot.String() &&
|
|
evt.Content.AsMember().Membership == event.MembershipInvite
|
|
}
|