Add Arrtrix runtime, config, onboarding, and webhook support
- Implement runtime package for bridge startup, config loading, and env overrides - Add onboarding package for management room welcome messages - Add matrixcmd package for command processing and help - Add webhook package with Radarr webhook support and validation - Extend connector config for webhooks and validation - Update default config and example config for new options - Add tests for new packages and config validation - Change database type default to sqlite3-fk-wal
This commit is contained in:
parent
eeedb5268a
commit
fe627f3aab
19 changed files with 1855 additions and 35 deletions
137
packages/arrtrix/pkg/onboarding/welcome.go
Normal file
137
packages/arrtrix/pkg/onboarding/welcome.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package onboarding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
log := zerolog.Ctx(ctx)
|
||||
sender, err := bridge.GetUserByMXID(ctx, evt.Sender)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to load sender for bot invite")
|
||||
return
|
||||
}
|
||||
if !sender.Permissions.Commands {
|
||||
return
|
||||
}
|
||||
|
||||
if err = bridge.Bot.EnsureJoined(ctx, evt.RoomID); err != nil {
|
||||
log.Err(err).Msg("Failed to accept invite to room")
|
||||
return
|
||||
}
|
||||
|
||||
members, err := bridge.Matrix.GetMembers(ctx, evt.RoomID)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to get members of room after accepting invite")
|
||||
return
|
||||
}
|
||||
if len(members) != 2 {
|
||||
return
|
||||
}
|
||||
|
||||
assignedManagementRoom := sender.ManagementRoom == ""
|
||||
if assignedManagementRoom {
|
||||
sender.ManagementRoom = evt.RoomID
|
||||
if err = sender.Save(ctx); err != nil {
|
||||
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 {
|
||||
log.Err(err).Msg("Failed to send welcome message to room")
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
56
packages/arrtrix/pkg/onboarding/welcome_test.go
Normal file
56
packages/arrtrix/pkg/onboarding/welcome_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package onboarding
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2/bridgeconfig"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
func TestComposeWelcomeMessageDefaults(t *testing.T) {
|
||||
out := composeWelcomeMessage(
|
||||
"Arrtrix",
|
||||
"!arr",
|
||||
id.UserID("@arrtrixbot:test"),
|
||||
bridgeconfig.ManagementRoomTexts{},
|
||||
false,
|
||||
true,
|
||||
)
|
||||
|
||||
for _, fragment := range []string{
|
||||
"Hello, I'm the Arrtrix bot.",
|
||||
"This room has been marked as your management room.",
|
||||
"Use `help` to see the commands available right now.",
|
||||
} {
|
||||
if !strings.Contains(out, fragment) {
|
||||
t.Fatalf("expected welcome output to contain %q, got:\n%s", fragment, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeWelcomeMessageTemplateValues(t *testing.T) {
|
||||
out := composeWelcomeMessage(
|
||||
"Arrtrix",
|
||||
"!arr",
|
||||
id.UserID("@arrtrixbot:test"),
|
||||
bridgeconfig.ManagementRoomTexts{
|
||||
Welcome: "Welcome to $bridge.",
|
||||
WelcomeConnected: "Talk to $bot with $cmdprefix help.",
|
||||
AdditionalHelp: "Custom footer for $bridge.",
|
||||
},
|
||||
true,
|
||||
false,
|
||||
)
|
||||
|
||||
for _, fragment := range []string{
|
||||
"Welcome to Arrtrix.",
|
||||
"Use `!arr help` to see available commands in this room.",
|
||||
"Talk to @arrtrixbot:test with !arr help.",
|
||||
"Custom footer for Arrtrix.",
|
||||
} {
|
||||
if !strings.Contains(out, fragment) {
|
||||
t.Fatalf("expected templated welcome output to contain %q, got:\n%s", fragment, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue